> Source: https://nexusatlas.dev/docs/concepts/traversal — Nexus Atlas developer documentation (Concepts). Converted from the HTML page; the page is canonical.

# NAT traversal & relays opt-in · default off

Two peers behind carrier-grade NAT cannot address each other at all — no endpoint to write in the config, because "I don't know where this peer is" is the problem itself. The traversal stack solves it Atlas-style: a relay link brings the connection up unconditionally, hole punching tries to add a direct path beside it, and the ordinary scheduler — not a reconnection — decides which one carries traffic. Everything here is opt-in: without `[traversal] enabled = true` the data path is byte-identical to a build without the feature.

## The load-bearing idea

Atlas did not adopt the WebRTC stack wholesale, because it does not want what WebRTC wants — one winning path. It wants *every working path in the bond*:

| Standard | In Atlas | Why |
|----------|----------|-----|
| STUN | Adopted. Binding requests, XOR-mapped addresses, NAT mapping classification — answered by Atlas relays themselves. | Small, correct, useful; no third-party server dependency. |
| TURN | Replaced by an Atlas-native relay server. | The relay forwards opaque, already-encrypted envelopes between clients that both pin its public key. It authenticates who may relay — it cannot read what it relays. |
| ICE | Replaced by rendezvous over the relay plus the existing scheduler. | A punched path is not a "new connection" — it's an existing link whose peer endpoint was discovered at runtime. The probes are the hole punch; the relay→direct switchover is a routine scheduling decision, with the relay staying in the bond as a warm standby. |

Consequence: failover between a relayed path and a punched direct path is the same gradual, probe-driven traffic shift as any other pair of links. Nothing reconnects, ever.

## Netcheck: know your NAT before you fight it

With traversal on, the daemon STUN-probes *out of each link's own socket* — the same 5-tuple its data uses, so the discovered mapping is the mapping that matters — classifies the NAT per link, and writes the report to `/tmp/atlas/<iface>.netcheck.json` (also at `GET /api/netcheck` and in `atlasd status`). It re-runs every `netcheck_interval_secs` (default 300).

> **Two servers, two different IPs — non-negotiable.** Distinguishing the NAT types that predict punch success requires observing your mapping from two distinct destination *addresses*; two ports on one IP cannot do it. Every Atlas relay answers STUN on both of its public addresses and advertises the sibling, so a single relay host is a complete observation pair.

## Relay links are ordinary links

A relay link is a `[[link]]` with `type = "relay"`, the relay's address, and its *pinned* public key — hijacking the relay's IP achieves nothing. It gets its own socket, its own probes, its own RTT, and competes in the scheduler like everything else. A peer reached through a relay needs no `endpoint` at all: its identity is its public key, and its address is discovered at runtime.

```
[traversal]
enabled = true
stun_servers = [
  "203.0.113.10:3478",   # the relay's first public address
  "203.0.113.11:3478",   # its second — a DIFFERENT IP, required
]

[[link]]
name = "relay-eu"
type = "relay"
bind_device = "wwan0"
bind_address = "0.0.0.0"
relay_server = "203.0.113.10:3478"
relay_pubkey = "RELAY_PUBLIC_KEY"    # pinned; printed by the relay's keygen
capacity = "20 Mbps"                 # relays are shared — cost them honestly

[[link]]
name = "direct-wwan"
bind_device = "wwan0"                # same modem as the relay link →
bind_address = "0.0.0.0"
listen_port = 51821                  # → a distinct port is REQUIRED
weight = 2

[[peer]]
public_key = "PEER_PUBLIC_KEY"
allowed_ips = ["10.0.100.2/32"]      # no endpoint — discovered via the relay
```

Housekeeping the stack does for you: relay envelopes add 66 bytes, so configuring any relay link automatically shrinks the fragment budget to keep relayed datagrams inside path MTU; relay keepalives hold the NAT binding open; and the relay-link/direct-link pair on one modem above demonstrates the [distinct-port rule](https://nexusatlas.dev/docs/concepts/architecture#links-and-endpoints), which config validation enforces.

## Hole punching: paid for by information, not timers

With `punch = true` (the default once traversal is on), peers exchange address candidates over the relay they already share and probe toward each other simultaneously, sorted in the same order on both sides so the attempts actually cross. A successful punch adds a direct path beside the relayed one — one hop shorter, and off the relay's bandwidth bill. A failed punch costs nothing: the relay keeps carrying traffic.

Retry discipline is deliberate: a candidate set is tried for one full lap and then rests; punching re-arms when something *changes* — a new reflexive address after a bearer handoff, a changed peer offer — rather than on a timer; and a punched path dying triggers a fresh netcheck instead of blind retries. Live links are never disturbed by any of it.

## Relay auto-election

Fleets configure several relays for redundancy; without help, N configured relays cost N live sessions of keepalives. With `auto_relay = true`, both ends of each peer relationship STUN-measure every relay, exchange their RTT vectors, and independently compute the same deterministic ranking — converging on the same shared relay set with no negotiation protocol. Exactly `relay_paths` (default 1) relay sessions stay active per peer; the rest are gated but configured, and failure promotes the next-best relay on both sides at once, make-before-break. A punched direct path is always additional.

## Self-hosted or Nexus Atlas-hosted

- **Self-hosted:** run the Atlas relay server — a single static binary with zero disk state — on a host reachable by both peers, with two public addresses for full NAT classification. Configure clients statically as above; no control plane is required.

- **Hosted:** Nexus Atlas operates relays in Europe and North America. With `discover = true` (default), a relay link that omits `relay_server`/`relay_pubkey` resolves one at runtime from a cryptographically signed relay map — so one config ships to a whole fleet with no hard-coded relay addresses, and `region` optionally pins selection ("eu-central"); unset picks the lowest RTT. Static per-link configuration always wins when present.

## Honest edges

- A pair of endpoint-dependent ("hard") NATs stays on the relay — Atlas does not attempt port-prediction punching. Correct, but some theoretically punchable pairs remain relayed.

- A punched path currently reports as `direct` in the stats — there is no `punched` marker distinguishing it.

- Traversal is verified end-to-end against simulated carrier-NAT topologies and on production relay hardware for election; phrase your own acceptance tests accordingly and run them on your bearers.
