Skip to content

CGNAT relay

Some internet connections never give you a public address you can forward a port to. Mobile data, 5G home broadband, and some fibre ISPs put you behind carrier-grade NAT (CGNAT), where many customers share one public IPv4 and no inbound port can reach you — automatic port-opening (PCP/NAT-PMP/UPnP) and manual port forwarding both have nothing to open.

The relay is the fallback that keeps these servers reachable. When your agent has no way to accept a direct connection, off-LAN clients reach it through the atreoLINK relay instead — without the relay ever being able to read your traffic.

On your home Wi-Fi the app still talks to your server straight over the LAN. With a forwarded or auto-opened port, off-LAN clients connect directly to your public address. The relay steps in only for the connections that would otherwise fail.

It’s on by default and fully automatic — there’s nothing to set up for the common case. The agent works out whether it has a usable public inbound path and only relays when it doesn’t:

  • If automatic port-opening or a manual forward gives you a reachable public address, the agent advertises that and doesn’t use the relay.
  • If the only address available is a CGNAT or private one (your public IP starts with 100.64.x.x, 10.x.x.x, etc.), the agent detects it and relays automatically.

Only when the agent decides it needs the relay does it ask atreoLINK for authorisation, which atreoLINK issues on demand over the agent’s existing control connection — telling it which relay to use and how to prove it’s allowed. An agent that has a direct path never asks, so a relay slot is reserved only for the servers that actually use the relay. You don’t configure a relay host or port.

CGNAT blocks unsolicited inbound connections, but it allows outbound ones and their return traffic — that’s the whole trick.

flowchart LR
  Client["Off-LAN client<br/>(app or WireGuard)"]
  Relay["atreoLINK relay<br/>(sees only ciphertext)"]
  Agent["Your server<br/>behind CGNAT"]
  Agent -. "1 — agent dials OUT and<br/>holds the link open" .-> Relay
  Client -- "2 — WireGuard UDP" --> Relay
  Relay -- "3 — back down the<br/>agent's open link" --> Agent
  1. The agent dials out. Because CGNAT permits outbound connections, the agent opens connections out to the relay and holds them open with regular keepalives. The relay never has to initiate a connection back to your server.
  2. A client speaks ordinary WireGuard to the relay’s public address. To the client it looks like any other WireGuard endpoint.
  3. The relay delivers those packets back down the agent’s already-open outbound link. A small forwarder on the agent hands them to its local WireGuard, and replies retrace the same path.

That forwarder is a blind byte-mover, not a second WireGuard: it holds no keys and never looks inside the packets. The relay, likewise, only ever sees encrypted WireGuard bytes plus its own routing framing.

Routing through the relay does not weaken your tunnel. The WireGuard session is still negotiated directly between your device and your server; the relay is just a postbox in the middle.

  • It forwards opaque WireGuard ciphertext and holds no WireGuard keys, so it cannot decrypt anything.
  • It keeps no database and is stateless — restart it and clients simply re-establish.
  • Authorisation is brokered by atreoLINK: the relay only serves a server that atreoLINK has vouched for and that proves it holds its own identity key. Clients aren’t separately authenticated by the relay — the end-to-end WireGuard handshake does that, and a bogus client just fails it.

The first time your agent needs the relay it’s assigned a stable relay address, and that address doesn’t change when the agent reconnects or the relay restarts. Because it behaves like an ordinary WireGuard endpoint, a relayed server works not just with the atreoLINK apps but with plain/vanilla WireGuard clients too — a manual WireGuard config you exported from the dashboard keeps pointing at the right place.

Privacy: what the relay can and can’t see

Section titled “Privacy: what the relay can and can’t see”

The relay is blind to your traffic — it can’t read app data or notification contents, exactly as atreoLINK itself can’t.

It is, however, a metadata-holding element of the data plane. While it carries your connection it can observe the client and server IP addresses involved, and the timing and volume of traffic — the kind of thing any network hop on the path sees. It can’t see what you’re doing, only that traffic is flowing. That’s a step beyond atreoLINK’s metadata-holding control plane, which is exactly why the relay is used only as a fallback for servers that can’t be reached any other way, and never for traffic that has a direct path.

Relayed connections share atreoLINK’s relay infrastructure, so a relayed server’s throughput may be throttled for fair use. Free-plan servers are throttled more heavily than paid plans, and relay use is subject to fair use — sustained or excessive use may be rate-limited, restricted, or cut off per the Acceptable Use Policy. Direct and LAN connections aren’t in the relay’s path and aren’t affected — one more reason a direct path is preferable when you can get one.

How to tell whether you’re being relayed

Section titled “How to tell whether you’re being relayed”
  • Agent logs. When the agent brings up a relay session it logs relay: session ready endpoint=<host:port> ingest=<host:port>. If a NAT’d gateway maps a port but hands back a carrier-NAT/private address, it also warns that the address is carrier-NAT/private and it’s using the relay.
  • A push notification. On a phone paired to the server you’ll get a one-off notification titled “Using relay (carrier-grade NAT)” the first time the agent decides it must relay, so you’re not left guessing why direct connections never worked.
Terminal window
docker logs atreoagent | grep -i relay

You never configure a relay host or port — the agent learns those automatically. A few agent settings tune the automatic behaviour:

SettingEffect
RELAY_ENABLED / relay.enabledOn by default. Set to false to opt this agent out of relaying entirely — with no direct path it’ll simply be unreachable off-LAN.
RELAY_FORCE / relay.forceOff by default. Keeps a relay session up even when a direct path exists, so a relay endpoint is always available to hand to clients (clients still prefer LAN/direct — the relay stays a fallback for them). Useful for offering a relay config alongside a direct one; not needed for normal use.
ENDPOINT_IP / endpoint_ipTells the agent it already has a reachable public address, so it advertises that direct endpoint and won’t relay. Use it when you’ve manually forwarded the port or have a static public IP.

See Environment variables and the config file for where these live.

WireGuard is a registered trademark of Jason A. Donenfeld.