Running pilot behind a firewall
When UDP is blocked, compat mode tunnels Pilot packets over HTTPS/WSS to the beacon — same overlay, different wire.
On this page
When you need compat mode
The default Pilot daemon binds a public UDP socket and reaches peers either directly or via beacon hole-punch. That model works on home ISPs, cloud VMs (GCP, AWS, Azure), and most corporate networks. It does not work on:
- Container PaaS without UDP exposure: Render, Railway, Vercel, Fly.io's per-app DNS routing, AWS Lambda — these platforms either don't route inbound UDP at all, or hide your daemon behind symmetric NAT that defeats hole-punch.
- Locked-down corporate networks where outbound UDP is firewalled but HTTPS to arbitrary internet hosts is allowed.
- Airport / hotel / conference wifi that blocks everything but TCP/443. As of v1.10.3, compat mode runs entirely on TCP/443 (no TCP/9000) — this case is fully covered.
If your daemon registers fine but heartbeats keep failing, or queries to specialists time out with relay-retransmit errors flooding the log, you're likely in one of these environments. Compat mode is the answer: same daemon binary, same overlay, just a different wire from your daemon to the beacon.
Enabling compat mode
Compat mode is opt-in via one CLI flag on the standalone pilot-daemon binary:
pilot-daemon -transport=compat
That's it — every other flag has a working default. As of v1.10.3 the daemon uses a single outbound TCP/443 connection for everything: the beacon WSS bridge AND the registry. Explicit form:
pilot-daemon \
-transport=compat \
-compat-beacon=wss://beacon.pilotprotocol.network/v1/compat \
-tls-trust=system \
-registry=registry.pilotprotocol.network:443 \
-registry-tls -registry-trust=system
Flag semantics:
-transport=compat— switches the tunnel transport from UDP to WSS. Default isudp; the daemon falls back to UDP behavior if this flag is unset.-compat-beacon <url>— the beacon's WSS endpoint. The default points at the managed public beacon (wss://beacon.pilotprotocol.network/v1/compat); self-hosted deployments override it.-tls-trust <mode>— see TLS trust. Defaults tosystem.-registry <host:port>— auto-defaults toregistry.pilotprotocol.network:443in compat mode. Overrides the standard34.71.57.205:9000UDP-mode default; you can still pass a custom registry host:port if you self-host.-registry-tls+-registry-trust=system— auto-enabled in compat mode. The registry channel is TLS-terminated on the same TCP/443 listener as the beacon WSS bridge (nginx SNI-routes the two hostnames behind one port).
The daemon also automatically forces relay_only=true when compat is enabled — peers will route to it via the beacon's relay path instead of trying direct UDP, which would fail anyway.
How it works
The compat-mode daemon opens outbound connections to only TCP/443 — multiplexed by SNI through a single nginx listener on the rendezvous host:
beacon.pilotprotocol.network:443— long-lived WebSocket Secure connection that carries the data plane (peer-to-peer Pilot frames, wrapped as binary WS messages).registry.pilotprotocol.network:443— TLS connection pool for the registry RPC channel (registration, heartbeats, resolve, hostname lookup). Same wire protocol as the legacy UDP-modetcp:9000, just wrapped in TLS.
nginx pre-reads the TLS ClientHello's SNI field and routes registry traffic to its TLS terminator (which proxies plain bytes to the existing registry server), while beacon traffic terminates on the existing WSS-aware vhost. No code change on the registry side; the daemon's -registry-trust=system path verifies the public Let's Encrypt cert.
After the TLS handshake, the daemon completes an Ed25519 challenge so the beacon can authenticate it against the registry's stored pubkey. From that point on, every Pilot UDP packet the daemon would have sent becomes one binary WS frame; every inbound binary WS frame becomes one Pilot packet returned by Recv.
The beacon transparently bridges between UDP peers and WSS peers. From a specialist's point of view, a compat-mode daemon looks identical to a symmetric-NAT peer — the beacon's existing relay logic delivers packets without any change to the specialist code.
End-to-end Ed25519 trust is unchanged. TLS provides the encrypted channel between daemon and beacon; Ed25519 still protects peer-to-peer identity and payload integrity, exactly as in UDP mode.
TLS trust + corp proxies
The public beacon at beacon.pilotprotocol.network currently presents a Let's Encrypt certificate (terminated by nginx on the rendezvous host). With -tls-trust=system (the default), the daemon verifies that cert against the OS trust store — the same way browsers do — and connects normally. The daemon logs a clear startup warning that this trust mode does not protect against TLS-intercepting proxies on the path.
A future release will ship the daemon binary with the Pilot Protocol root CA cert embedded. At that point the default flips to -tls-trust=pinned, which verifies the beacon's leaf cert against only this root — no public CA bug, DNS hijack, or Let's Encrypt issuance mistake can MITM your daemon. The escape hatch for that future state (for daemons behind TLS-intercepting corp proxies like Fortinet, Zscaler, BlueCoat, Palo Alto, Cisco Umbrella) will be to pass -tls-trust=system explicitly.
End-to-end Ed25519 protects peer identity and payload integrity in both trust modes. A TLS-intercepting proxy can read or censor relay traffic on the wire but cannot forge a specialist's signed reply.
Limits
- Latency: Compat-mode traffic always traverses the beacon — there is no direct path. Expect ~50-200 ms one-way to the nearest beacon region, plus the beacon → specialist hop. UDP mode reaches specialists directly when NAT allows it.
- Bandwidth: Compat traffic costs the beacon roughly 2× the bytes of UDP relay (TCP/TLS framing overhead, and the beacon pays for both legs). Sustained heavy throughput is better served by a UDP-capable environment.
- Hostile-state DPI: Networks that block arbitrary outbound TLS (Great Firewall, some government deployments) will block compat mode. Domain fronting / ECH / Snowflake-style obfuscation is out of scope.
- One beacon per session: The daemon holds one WSS connection at a time. On disconnect it reconnects with exponential backoff, optionally selecting a different beacon hostname from the configured list.
For the full design — transport matrix, PKI, wire format, rollout plan — see docs/SPEC-compat-mode.md in the repo.