> Source: https://nexusatlas.dev/guides/relay-directory — Nexus Atlas developer documentation (Guides). Converted from the HTML page; the page is canonical.

# Relay directory and federation opt-in · default off

Two nodes in different regions each attach to the relay nearest them — one in `eu-central`, one in `na-east` — and still reach each other, with no session across the ocean from either end. Two mechanisms make it work. The **relay directory** tells a node which relay a peer is on, through a presence record sealed so that only that peer can read it. **One-hop federation** lets a node's own relay forward to the peer's relay, so neither end has to attach to the other's. Neither gives the control plane a list of which key is on which relay. Read [NAT traversal & relays](https://nexusatlas.dev/docs/concepts/traversal) first if relay links are new.

> **What you need:** `[traversal] enabled = true` with at least one relay link, and the peers you want to reach listed as static `[[peer]]` entries — records are published and looked up for static peers only. Federation additionally needs `[crypto] e2e = true` on the nodes and `[federation] enabled = true` on both relays of a path.

## 1 — How a node finds a peer's relay

- **Publish.** While a node holds at least one established relay session, it posts one presence record per static peer to the control plane: the relays it is on, sealed to that peer and signed by the poster. It re-posts every 300 s and 2 s after its set of established relays changes. Each record asks for a 590 s lifetime, so a node that loses every relay session simply stops being findable within ten minutes. A record names at most 8 relays, and only relays the node holds an established session with.

- **Look up.** When a static peer has been absent for 30 s — every established relay that can address it answers `UNKNOWN_DEST` — the node fetches that peer's record. A peer never seen since start-up (never reached, and no relay has ever shown it present) is looked up after 5 s instead. A node posts its own records 2 s after its first relay session, so when both ends start together the record is already there and a cold bring-up through the directory takes seconds. From then on: one lookup per 60 s while the peer is absent, none while it is present, and none after 10 misses in a row until the peer's next absence.

- **Attach.** For each relay the record names that this node does not hold, the node arms a [directory slot](https://nexusatlas.dev/guides/relay-directory#slots) with it and the normal handshake follows. The relay's key is pinned from the record; when the node has a signed relay map cached, the relay must be listed in it, and its address and region are taken from the map. A relay the map does not list is refused and journalled as `peer.directory_relay_refused`.

The record format, the server's validation and what it stores are on the [Presence API](https://nexusatlas.dev/docs/presence-api) reference.

## 2 — Turn on the directory

| Key | Default | What it does |
|-----|---------|--------------|
| directory | the value of auto_relay | Publishes sealed presence records for every static peer while a relay session is held, and looks absent peers up. Needs enabled. |
| directory_url | unset | Control-plane base URL; records go to {base}/v1/presence. Unset uses relay_map_url, else https://nexusatlas.net/api. |
| directory_slots | 2 | Relay links reserved at start for relays learned from the directory. 0 turns lookups off while records are still published — unless federation is on, which looks peers up for its via hint. |

With relay auto-election on, the directory comes with it. A minimal node:

**/etc/atlas/atlas.toml (additions)**

```
[traversal]
enabled = true
auto_relay = true            # directory follows auto_relay unless set
directory = true             # explicit — the same as the default here
directory_slots = 2

[[link]]
name = "relay-home"
type = "relay"
bind_device = "wwan0"
bind_address = "0.0.0.0"
relay_server = "203.0.113.10:3478"
relay_pubkey = "RELAY_PUBLIC_KEY"    # the relay near this node

[[peer]]
public_key = "PEER_PUBLIC_KEY"       # static peer: records are kept for it
allowed_ips = ["10.0.100.2/32"]      # no endpoint — found through the relays
```

## 3 — Directory slots

A node's links are fixed when it starts, so the relay links a lookup may need are reserved then. Each slot is a copy of the first enabled relay link — the same device and bind address — named `directory-relay-1`, `directory-relay-2` and so on, on a port of its own above the highest listen port in the config. At most 8 are reserved, and none when the config has no `[[peer]]` or no relay link. A slot stays parked until a record names a relay this node does not hold; the election engine never gates an armed slot, it ranks it.

- **Release.** A slot whose relay has shown no present peer for 10 minutes is released — its session closed, the link parked again — and journalled as `relay.directory_slot_released`.

- **Re-arm.** When every slot is armed and a lookup names a relay this node does not hold, an idle slot (no peer present on it now) is re-armed with that relay. Lookups therefore continue after every slot is armed, at the same cadence.

## 4 — Turn on federation

With federation, a node does not need to attach to the peer's relay at all. Frames for a peer that is not on the node's relay go to that relay as a `ForwardVia` naming the peer's relay; the node's relay forwards them exactly one hop, over an inter-relay session, and the peer's relay delivers them as an ordinary `Deliver`. The peer does the mirror image with this node's record.

| Key | Default | What it does |
|-----|---------|--------------|
| federation | false | Sends frames for a peer homed on another relay through this node's relay with a via hint, and lets the election rank federated paths. Needs enabled and the directory. |
| federation_allow_plain | false | Uses federated paths even when [crypto] e2e is off. |

**/etc/atlas/atlas.toml (additions)**

```
[crypto]
e2e = true                   # required for federated paths

[traversal]
enabled = true
auto_relay = true
federation = true            # directory is on through auto_relay
# federation_allow_plain = false
```

**The e2e rule.** A second relay is a second observer of relayed traffic. A node therefore uses a federated path only when `[crypto] e2e` seals its egress, or with `federation_allow_plain = true` set knowingly — the Atlas datagrams stay Noise-encrypted between the peers, but metadata and timing are then visible to two relay operators. The rule applies to each node's own sends, so set `e2e` on both peers. With federation on and neither condition met, the snapshot reads `traversal.federation.plain_blocked: true`.

How the far relay is chosen, for each established relay link and static peer:

- **When the election ranks the peer jointly,** it ranks relays both ends hold at the cost of the two legs, and federated pairs at our leg plus the relay-to-relay RTT from the map's matrix plus the peer's leg, on one scale. `relay_paths` counts paths of either kind; make-before-break is unchanged. A node follows the elected federated path whose near relay is the link's relay.

- **Otherwise the peer's record decides:** no hint when the record names this link's relay, else the named relay this node does not hold with the lowest relay-to-relay RTT in the map's matrix.

- A relay this node holds is never a via target — the common relay is used instead. The hint applies only while the peer is not present on the link's relay; a peer that is present always gets a plain `Forward`.

- With federation on and permitted, the first directory hit of an absence episode arms no slot: the node's relay forwards via the peer's. If the peer is still absent a minute later — the node's relay does not federate, say — the second hit arms a slot as in section 3.

- When the node's relay answers `VIA_UNKNOWN` or `FEDERATION_OFF`, the node sends plain `Forward`s on that relay for 60 s, then retries the hint, and journals `relay.federation_refused` once per refusal episode.

With federation on, the fragment payload is capped at 1317 bytes, so every leg of a federated path stays one 1500-byte IPv4 datagram. Overheads per leg are on the [relay protocol](https://nexusatlas.dev/docs/relay-protocol#overheads) reference.

## 5 — The relay side

A relay forwards for other relays' clients only when its operator turns it on. Both relays of a federated path need it.

**/etc/atlas/relay.toml (additions)**

```
[federation]
enabled = true
max_pairs = 16               # inter-relay sessions held at once, each direction
pair_mbps = 20               # per relay pair and direction; 0 = unlimited
# map_url = "https://nexusatlas.net/api/v1/relays"   # default: {registry.url}/v1/relays
# map_file = "/etc/atlas/relay-map.json"             # the signed map on disk instead
# map_poll_secs = 30                                 # default: the registry heartbeat period
```

| Key | Default | What it does |
|-----|---------|--------------|
| enabled | false | Forwards a ForwardVia one hop to the named relay. Off, a ForwardVia for a key this relay does not hold is answered FEDERATION_OFF. |
| max_pairs | 16 | Inter-relay sessions opened at once (outbound) and accepted at once (inbound). The least recently used idle one makes room. Must be at least 1. |
| pair_mbps | 20 | Rate cap per relay pair and direction, Mbit/s of forwarded payload; 0 = unlimited. |
| allow_plain | false | Reported in the heartbeat; the e2e rule itself is enforced by atlasd. |
| map_url | {registry.url}/v1/relays | Where the signed relay map is fetched. |
| map_file | unset | Reads the signed map from this file instead, re-read every poll. |
| map_poll_secs | the registry heartbeat period | Map refresh period. |

A relay with `enabled = true` refuses to start without a signed relay map: set `[registry]`, `map_url` or `map_file`. Peer relays are known only from that map, verified with the same pinned key clients use; a frame from a relay key the map does not list is dropped. Inter-relay sessions are Noise IK between the relays' static keys, opened on the first `via` naming a relay, keepalived every 20 s, declared dead after 45 s of silence and closed after 5 minutes unused. The sender's per-session forward quota is charged as for any `Forward`, and each relay pair has its own `pair_mbps` bucket on the sending relay and on the receiving one.

A federating relay also measures its round-trip time to every other relay in the map — a STUN binding request every 60 s — and reports it in its heartbeat. The control plane publishes the pairs both relays report as the map's relay-to-relay matrix, which is what the election's federated cost reads. A relay without federation support (relay contract 1) drops a `ForwardVia` silently — one with federation turned off answers `FEDERATION_OFF` instead — and the node then reaches the peer through a directory slot once a lookup a minute later still finds it absent.

## 6 — Verify

```
curl -s http://node-a:9800/api/stats | jq '.traversal.directory, .traversal.federation'
```

```
{
  "enabled": true,
  "published": 42,
  "publish_failures": 0,
  "lookups": 3,
  "hits": 1
}
{
  "enabled": true,
  "plain_blocked": false,
  "paths": 1,
  "frames_via": 18234,
  "errors": 0
}
```

On the relay link that carries a federated path, `links[].relay.via` names the far relay (`server`, `pubkey_short`, `region`, `city`), and the peer's `relay_presence[]` entry for that link carries `via_relay`. The dashboard shows the path as two relays in series on the topology. In the journal:

```
curl -s "http://node-a:9800/api/journal?code=relay.federated_path_up"
curl -s "http://node-a:9800/api/journal?code=peer.directory_hit"
```

Every new snapshot field and journal code is listed on the [relay protocol](https://nexusatlas.dev/docs/relay-protocol#snapshot) reference.

## Boundaries

A federated path is exactly one relay-to-relay hop: a frame that arrived from a relay is delivered or refused, never sent on. Records are kept for static peers only, the number of directory slots is fixed when the daemon starts, and federated paths need `[crypto] e2e` or `federation_allow_plain`. The full list lives on [Known limitations](https://nexusatlas.dev/limitations#traversal).
