NAT Traversal

Network Address Translation (NAT) is the mechanism that lets many devices share one public IP address (RFC 1631, Egevang & Francis 1994), originally introduced as a stopgap for IPv4 address exhaustion and now permanently ubiquitous in residential, mobile, and corporate networks. The byproduct: every endpoint behind a NAT has no inbound reachability — outsiders cannot initiate connections to it without a pre-existing outbound mapping in the NAT’s translation table. This breaks Peer-to-Peer (P2P) communication, which inherently requires both ends to receive packets they did not specifically solicit. NAT traversal is the discipline of getting around that limitation. The Internet’s working answer is a triad of protocols: STUN (Session Traversal Utilities for NAT, RFC 8489) — a public server tells you what your packets look like from outside the NAT, so you can advertise that public-side address to peers; TURN (Traversal Using Relays around NAT, RFC 8656) — a public relay forwards packets between you and your peer when the NATs cannot be tricked into direct delivery; and ICE (Interactive Connectivity Establishment, RFC 8445) — the meta-protocol that gathers all candidate addresses (host, server-reflexive via STUN, relayed via TURN), exchanges them via signaling, and tries them in priority order to find one that works. The triad is the load-bearing infrastructure underneath Web Real-Time Communication and every other Internet P2P system in active use, including peer-to-peer game lobbies, VoIP softphones, file-sharing protocols (BitTorrent’s NAT-PMP/PCP fallback to manual port mapping is structurally similar), and any Video Conferencing System Design product offering peer-to-peer one-on-one calls.

1. The Problem — Why NAT Defeats Peer-to-Peer

Imagine the textbook IP stack: every device on the Internet has its own globally-routable IP address, and any device can send UDP packets to any other device by their IP and port. Originally — and this is what TCP/IP was designed for — every host had a unique 32-bit IPv4 address, and connecting from one host to another was just sendto(remote_ip, remote_port).

In the early 1990s, IPv4 address exhaustion became visible — there are only ~4.3 × 10⁹ IPv4 addresses, with hundreds of millions reserved for special uses, and the actually-usable pool was running low as Internet uptake exploded. RFC 1631 (1994) proposed NAT as a stopgap: a router with one public IP could service many private hosts by rewriting their packets on the way out. The router maintains a translation table mapping (private_ip, private_port) ↔ (public_ip, ephemeral_public_port). Outgoing packets get their source rewritten; incoming responses get their destination rewritten.

This works fine for client-initiated connections (the table entry is created by the outbound packet). It does not work for unsolicited inbound packets — there’s no table entry, the NAT doesn’t know which private host to forward to, the packet is dropped.

The result: for two hosts behind separate NATs, neither can be the “server” of the other. P2P UDP simply doesn’t work without help. (TCP P2P faces the same issue; the NAT issues are symmetric.)

The original assumption was that NAT was temporary, IPv6 (with its 128-bit address space — 3.4 × 10³⁸ addresses, enough for ~5 × 10²⁸ per person on Earth) would replace it, and P2P-friendly addressing would return. Three decades later, NAT is still the default for residential and mobile networks worldwide. IPv6 deployment is partial; even IPv6 networks frequently sit behind Carrier-Grade NAT (CGN). NAT traversal is permanent infrastructure.

2. The Four NAT Types — How They Differ in Restrictiveness

Different NAT implementations differ in how loose or strict they are about which inbound packets they’ll deliver. RFC 3489 (the original STUN spec) and Ford et al.’s 2005 USENIX paper “Peer-to-Peer Communication Across NATs” classify them into four canonical types, ordered from loosest to strictest:

2.1 Full-Cone NAT

The NAT establishes a mapping (private_ip, private_port) → (public_ip, ephemeral_port) when an outbound packet is sent. Any external host can then send packets to (public_ip, ephemeral_port) and the NAT will forward them to the private host. The mapping is one-to-one and unconditional once created.

This is the most P2P-friendly NAT. STUN alone solves it: A asks the STUN server “what’s my public address?”, learns it, sends to B “I’m at 1.2.3.4:60000”, and B can send packets to A there. Almost extinct in practice; rare modern home routers expose this.

2.2 Restricted-Cone NAT

Same mapping behavior, but the NAT only forwards inbound packets from external hosts whose IP the private host has previously sent to. If A’s private host sent a packet to 5.6.7.8, the NAT will accept inbound packets from 5.6.7.8 (any port), but not from 9.10.11.12.

Workable with STUN if both peers send each other a packet first (“hole punching”). The first outbound packet creates the mapping and authorizes the peer’s IP; reciprocal handshake completes the connectivity.

2.3 Port-Restricted-Cone NAT

The NAT only forwards inbound packets from (external_ip, external_port) pairs the private host has previously sent to. Stricter than restricted cone — even the port must match.

Still tractable with hole punching: if A sends to B’s (ip, port) and B sends to A’s (ip, port), both NATs create mappings authorizing the precise external (ip, port) of the other side. The two mappings open simultaneously and packets flow.

2.4 Symmetric NAT — the Hard One

The NAT allocates a different public-side mapping for each (internal_addr, external_addr) pair. So when A sends to STUN server S from internal port 60000, the NAT might map to public port 33333. When A then sends to peer B from the same internal port 60000, the NAT maps to a different public port — say 44444. The mapping A learned from STUN (33333) is useless for talking to B; B can’t reach A through 33333 because the NAT routes inbound 33333 only to the previously-recorded peer (the STUN server).

Symmetric NATs were once primarily found in carrier-grade NAT and corporate firewalls but have spread because they are arguably more secure (each external endpoint sees a distinct mapping, so cross-flow attacks are harder). They defeat hole punching: two peers behind symmetric NATs cannot find a working direct path. The only fallback is TURN — relay the packets through a public server.

The 2025 reality: per various measurements (the Mozilla CT logs in 2017, Twilio’s traversal stats), roughly 8–15% of WebRTC connection attempts hit symmetric-NAT pairs and require TURN.

2.5 Why the four-type taxonomy is obsolete (but still used)

The “four NAT types” naming dates to RFC 3489 (the original 2003 STUN specification) and is now formally deprecated. RFC 4787 (Audet & Jennings, January 2007 — “NAT Behavioral Requirements for Unicast UDP”) says so explicitly:

“STUN [RFC 3489] used the terms ‘Full Cone’, ‘Restricted Cone’, ‘Port Restricted Cone’, and ‘Symmetric’ to refer to different variations of NATs applicable to UDP only. Unfortunately, this terminology has been the source of much confusion, as it has proven inadequate at describing real-life NAT behavior.”

The deeper problem is that the cone taxonomy conflates two genuinely independent axes that real NATs vary along separately. RFC 4787 splits them apart:

  • Mapping behaviorhow the public-side (ip, port) is chosen when a private host sends out. The three values are Endpoint-Independent Mapping (the NAT reuses the same public mapping regardless of the external destination — this is what makes hole punching possible, because the address you learned from STUN is the address a peer can use), Address-Dependent Mapping (the mapping is reused only for the same external destination IP, any port), and Address and Port-Dependent Mapping (the mapping is reused only for the identical external IP and port — equivalent to the old “symmetric” behavior, and the killer for hole punching).
  • Filtering behaviorwhich inbound packets the NAT will deliver through an existing mapping. The parallel three values are Endpoint-Independent Filtering (deliver from anyone — the old “full cone” filter), Address-Dependent Filtering (deliver only from external IPs the host has sent to — the old “restricted cone” filter), and Address and Port-Dependent Filtering (deliver only from external (ip, port) pairs the host has sent to — the old “port-restricted cone” filter).

The old four “cones” are really just a few of the combinations of these two axes: “full cone” = Endpoint-Independent Mapping + Endpoint-Independent Filtering; “symmetric” = Address-and-Port-Dependent Mapping (plus typically the strictest filtering). Crucially, the mapping axis is the one that determines whether hole punching can work at all — Endpoint-Independent Mapping is hole-punchable; Address-and-Port-Dependent Mapping is not. RFC 4787 then issues behavioral requirements: a well-behaved NAT SHOULD use Endpoint-Independent Mapping (REQ-1) precisely so that STUN-and-hole-punching keeps working. The four-type names survive as field shorthand because they are shorter and “symmetric NAT defeats hole punching” is a useful one-liner — but when precision matters (e.g., reading a NAT’s published behavior), the RFC 4787 mapping/filtering vocabulary is the correct one.

3. STUN — Session Traversal Utilities for NAT

STUN (RFC 8489, supersedes RFC 5389 from 2008) is a thin, stateless protocol whose primary use is letting a client learn its own public-side (IP, port) as seen from outside its NAT.

3.1 The basic mechanism

A STUN server is a public-IP UDP server listening on port 3478 (or 5349 for STUN-over-TLS). The protocol is request/response:

Client → STUN server: STUN Binding Request (UDP packet, 20-byte header)
STUN server → Client: STUN Binding Response containing XOR-MAPPED-ADDRESS attribute
                       — the source IP:port the server saw the request from

The XOR-MAPPED-ADDRESS attribute is the punchline: the STUN server reports back what address it saw, and that’s the client’s public-side mapping. The client now knows “from the outside, I appear to be at 1.2.3.4:60000”. (XOR with the magic cookie obfuscates the address against NATs that try to be helpful by rewriting addresses inside payloads.)

The “transaction ID” in the request matches the response; this defeats off-path attackers from injecting forged responses, since they’d need to guess the 96-bit transaction ID.

3.2 What STUN cannot do

STUN does not do connectivity setup itself. It just gives you an address. To actually connect to a peer:

  1. A learns its public address Ap from STUN.
  2. B learns its public address Bp from STUN.
  3. A and B exchange Ap and Bp via signaling.
  4. A sends a packet to Bp and B sends a packet to Ap — simultaneously enough that the NATs create mappings and the packets cross successfully (hole punching).

This works for full-cone, restricted-cone, and port-restricted-cone NATs. It does NOT work for symmetric NAT pairs, because the mapping Ap was for the STUN server, not for B; when A sends to B the NAT allocates a fresh mapping that B doesn’t know about.

3.3 STUN as part of ICE

In the ICE framework, STUN is also used during connectivity checks: the connectivity check between two candidates is itself a STUN binding request, with extra ICE-specific attributes (USERNAME, MESSAGE-INTEGRITY for HMAC authentication, PRIORITY for tiebreaking, USE-CANDIDATE for nominating the working pair). The STUN server function and the STUN connectivity-check function are protocol-identical but used differently.

3.4 Cost economics

STUN bandwidth is negligible. Each binding request/response is ~30 bytes. A million-user video conference service consumes a tiny fraction of a STUN server’s capacity. Public open-source STUN servers (e.g., stun.l.google.com:19302) handle billions of requests/day with modest infrastructure. Most WebRTC services run their own STUN servers as a side-effect of running TURN servers (see below).

4. TURN — Traversal Using Relays around NAT

TURN (RFC 8656, supersedes RFC 5766 from 2010) is the fallback when STUN-based hole punching fails: a public-IP server relays packets between the two peers. Each peer sends to the TURN server; TURN forwards to the other peer.

4.1 The mechanism

Client A ──── outbound to TURN server ────→ TURN server ──── outbound to Client B ────→ Client B
Client B ──── outbound to TURN server ────→ TURN server ──── outbound to Client A ────→ Client A

Both peers connect outbound to the TURN server (which the NAT happily lets through, creating mappings). The TURN server is now the rendezvous: each peer sends packets to the server with a “send to peer X” wrapper; the server unwraps and delivers to the corresponding peer’s NAT-mapped address.

4.2 Allocations and channels

A TURN client first asks for an allocation: “give me a relayed transport address (turn_ip, ephemeral_port) that I can advertise to my peer.” The TURN server allocates a port and authenticates the client (TURN requires authentication; it costs the operator real money to relay traffic, so anonymous relay is rejected). The client now has a relayed address.

Then the client asks for permissions per peer: “permit IP X to send to my allocated address.” Without this, the TURN server drops inbound from unknown peers (anti-amplification protection).

Then the client can either send each packet wrapped in a Send Indication (per-packet overhead of ~40 bytes header) or — more efficient — establish a channel (channel binding) and send packets with a 4-byte channel header.

Inbound packets to the relayed address are wrapped in Data Indications (or channel headers if a channel exists) and delivered to the client.

4.3 The cost

TURN is expensive. The TURN operator pays for:

  1. Inbound bandwidth from each peer to the TURN server.
  2. Outbound bandwidth from the TURN server to the other peer.

For a video call relayed through TURN, this means 2× the call’s bandwidth is paid by the operator. At scale this becomes the dominant infrastructure cost. Twilio sells TURN as a service ($0.40/GB at one point — verify current pricing); Cloudflare Calls offers TURN via their own infrastructure; many WebRTC startups have died of TURN bandwidth blowing through their margins.

Open-source TURN: coturn is the reference implementation, used by Discord, Jitsi, and many production deployments. A single coturn server on a 10 Gbps NIC can relay ~10 000 simultaneous voice flows or ~500 video flows.

4.4 TURN over TCP and TLS

TURN supports TCP (port 3478) and TLS (port 5349) transports as well as UDP. These are critical fallbacks for corporate networks that block UDP entirely or block UDP to non-standard ports. TURN-over-TCP-over-TLS-on-port-443 is the “guaranteed to work everywhere” fallback — it looks like ordinary HTTPS to the firewall.

4.5 TURN data flow latency overhead

Direct P2P latency: 1 round trip ≈ 50 ms intra-region, 200+ ms intercontinental. TURN-relayed latency: 2 round trips (peer → TURN → peer) ≈ 1.5–2× the direct path, plus the TURN server’s processing ≈ ~5 ms. For a Tokyo-to-NewYork call, TURN in Frankfurt would actually be slower than direct P2P; TURN servers must be regionally placed close to the median peer location to minimize impact.

5. ICE — Interactive Connectivity Establishment

ICE (RFC 8445, supersedes RFC 5245 from 2010) is the meta-protocol that orchestrates STUN, TURN, and direct candidate gathering into a single algorithm that finds the best working network path between two peers.

5.1 Candidate types

For a single network interface, ICE gathers up to four kinds of candidates:

TypeSourceCost to use
HostLocal interface IP:port (private if behind NAT)Free, lowest latency
Server-reflexive (srflx)Public IP:port via STUNFree, ≈ same as host once routed
RelayedTURN server’s allocated addressBandwidth costs; ~5–20 ms latency penalty
Peer-reflexive (prflx)Discovered during connectivity checks (a STUN binding sent over an existing candidate pair reveals an unexpected mapping)Free, low-latency

A device with multiple interfaces (Ethernet + WiFi + cellular) gathers candidates for each. So in a complex case ICE may have a dozen or more candidates per peer.

5.2 Priority

Each candidate is assigned a priority per the formula in RFC 8445 §5.1.2.1:

priority = (2^24)·(type preference) + (2^8)·(local preference) + (2^0)·(256 - component_id)

where:

  • type preference ∈ [0, 126]: typically 126 for host, 100 for srflx, 110 for prflx, 0 for relayed
  • local preference: tiebreaker between candidates of the same type (e.g., wired Ethernet > WiFi)
  • component_id: 1 for RTP, 2 for RTCP

The priority ordering means ICE prefers host > peer-reflexive > server-reflexive > relayed. Direct paths beat relayed ones; LAN beats WAN.

5.3 Connectivity checks

After candidates are gathered, both peers sort the candidate pairs (combinations of one local + one remote candidate) by priority and run STUN binding requests over each pair to test if it works. The check serves two purposes: it confirms reachability, and (for restricted/port-restricted NATs) it punches the hole.

The first pair that succeeds and has the highest priority is “nominated” as the active pair. Media flows over it.

ICE’s full algorithm includes:

  • Trickle ICE. Don’t wait for all candidates to be gathered before sending any to the peer; trickle them as discovered. Reduces setup latency.
  • Aggressive nomination (deprecated) vs regular nomination. Aggressive: nominate the first working pair. Regular: try several and pick the best. WebRTC uses regular nomination since RFC 8445.
  • ICE restart. When a network change happens (WiFi → cellular handoff), the ICE state is invalidated and re-runs. Triggered by changing the ufrag/pwd in a renegotiated SDP.

5.4 The typical sequence

sequenceDiagram
    participant A as Peer A
    participant SIG as Signaling
    participant B as Peer B
    participant STUN as STUN Server
    participant TURN as TURN Server

    A->>STUN: Binding Request
    STUN-->>A: srflx candidate: 1.2.3.4:60000
    A->>TURN: Allocate
    TURN-->>A: relayed candidate: 5.6.7.8:50000

    Note over A: candidates = { host: 192.168.1.5:60000,<br/>srflx: 1.2.3.4:60000, relay: 5.6.7.8:50000 }

    A->>SIG: trickle: candidate=host
    A->>SIG: trickle: candidate=srflx
    SIG->>B: forwards candidates
    A->>SIG: trickle: candidate=relay
    SIG->>B: forwards candidate=relay

    B->>STUN: Binding Request
    STUN-->>B: srflx candidate: 9.10.11.12:50000
    B->>SIG: trickle: candidates
    SIG->>A: forwards B's candidates

    Note over A,B: connectivity checks (STUN bindings over each pair)
    A->>B: STUN over (A_host, B_host) → fails (different LANs)
    A->>B: STUN over (A_srflx, B_srflx) → succeeds! (port-restricted, hole punched)
    Note over A,B: Pair nominated, media flows over (A_srflx, B_srflx)

Walking through the diagram. A queries the STUN server to learn its server-reflexive address. A also allocates a TURN relay (just in case). A enumerates its three candidates and trickles each over the signaling channel as soon as it’s known. B does the symmetric thing. Both sides now have each other’s candidate lists. They run connectivity checks over each pair, in priority order. The host-host pair fails — A and B are on different LANs, so A’s 192.168.1.5 is unreachable from B. The srflx-srflx pair succeeds: each side’s STUN binding request to the other punches the hole and the response confirms reachability. ICE nominates the (srflx, srflx) pair; media flows through it. The relay candidates aren’t used; the TURN allocation can be released.

If both sides had been behind symmetric NATs, the srflx-srflx pair would also fail (because neither side’s NAT would route inbound from the other’s IP without a prior mapping to that exact IP, and the STUN-server-induced mapping doesn’t apply), and ICE would fall back to the relay-* pairs.

5.5 ICE candidate-gathering latency

Connectivity checks take 1–3 seconds in typical cases, sometimes longer:

  • STUN round trips to each STUN server (~50 ms each, multiple parallel)
  • TURN allocation (2 round trips for authentication challenge + allocate, ~150 ms)
  • Connectivity check probes per candidate pair (1 RTT each, ~50 ms)
  • Each pair tested sequentially (or in parallel up to a limit)

The ~1–3 s of ICE setup is one of the biggest contributors to “feels slow to start a call” perception in WebRTC apps. Aggressive optimization (parallel candidate gathering, trickle ICE, pre-warmed TURN allocations on signaling-server connect) shrinks it.

6. Failure Rates in Production

Real-world ICE success statistics, drawn from vendor disclosures (Twilio NAT-traversal write-ups, Cloudflare WebRTC infrastructure posts, and various WebRTC operator blog posts):

Uncertain — production failure-rate figures

Verify: the specific percentages in this section (85–90% direct P2P, 10–15% TURN-relayed, <1% total failure, and the per-network-type breakdown below). Reason: these are widely-repeated industry rules-of-thumb but were not pinned to a dated primary measurement during this pass — the cited vendor reports are not directly linked and the true numbers drift over time and by user population. To resolve: cite a specific dated Twilio/Cloudflare/vendor publication or a peer-reviewed measurement study, or relabel the figures explicitly as order-of-magnitude estimates. The qualitative claim (most connections go direct; a single-digit-to-mid-teens minority needs TURN; total failure is small when TURN is healthy) is robust; the exact percentages are the uncertain part. uncertain

  • Direct P2P (host-to-host or srflx-to-srflx) succeeds for 85–90% of consumer-network connection attempts.
  • TURN-relayed connections account for 10–15% — the residue when both sides are behind restrictive NATs or networks.
  • Total connection failure rate: < 1% when TURN is correctly configured and reachable.

The breakdown varies sharply by network type:

  • Pure residential networks: 92–95% direct P2P success.
  • Mobile networks (LTE, 5G): 80–85% direct (more carrier-grade NAT).
  • Corporate networks with strict firewalls: 30–60% direct; the rest need TURN-over-TLS-port-443.

When TURN is unavailable (misconfiguration, the operator chose not to provision it), the residual ~10–15% of users get a black screen and “could not connect” errors. Operators routinely under-provision TURN to save cost, and users complain on support forums.

7. The IPv6 Question

Naive intuition: with IPv6’s massive address space, every device gets a globally-routable address, NAT is unnecessary, P2P just works, and STUN/TURN/ICE become obsolete.

The reality is more complicated:

  1. IPv6 deployment is partial. Per Google’s IPv6 statistics, as of 2024 ~45% of users access Google over IPv6 (varies enormously by country: Germany 70%+, China <10%). Heterogeneous v4/v6 networks mean any P2P system must support both stacks, and must support the case where one peer has v6 and another only v4.

  2. Carrier-Grade NAT exists for IPv6. Mobile carriers and ISPs increasingly deploy 464XLAT, NAT64, and other v6-to-v4 translation schemes that introduce NAT-like restrictions even on IPv6 traffic. CGN is sometimes called “the NAT we still need”.

  3. Stateful IPv6 firewalls. Even routable IPv6 addresses commonly sit behind stateful firewalls that drop unsolicited inbound packets. This isn’t NAT, but its effect on P2P is identical: unsolicited inbound is rejected.

The takeaway: NAT traversal is here to stay. IPv6 reduces the severity (more candidates work directly, fewer fall back to TURN), but the protocols and infrastructure remain necessary.

8. Pitfalls

  1. Under-provisioned TURN. Common cost-cutting mistake; ~10% of users silently fail. Test from restrictive networks (corporate VPN, mobile hotspot) before declaring the service “works”.
  2. TURN bandwidth bills. Twilio’s published rates are $0.40–0.80/GB for TURN traffic in 2023. A WebRTC startup with unexpectedly high TURN-fallback rates can rack up bills exceeding their compute spend.
  3. TURN credentials leaking via signaling. TURN credentials issued to clients via signaling are short-lived but if leaked allow free relay. Always use ephemeral, time-limited credentials (TURN REST API: HMAC-derived per-session credentials).
  4. STUN server load. Public free STUN servers (stun.l.google.com) work for testing but should not be relied on for production. Run your own. They’re cheap (one server can handle billions of requests/day).
  5. ICE candidate gathering blocking the UI. Browsers parallelize candidate gathering, but slow STUN responses or TURN authentication failures can stall ICE for seconds. Trickle ICE so partial candidates flow even if some servers are slow.
  6. Symmetric NAT misclassification. Some networks behave like symmetric NAT for some flows but not others. ICE’s empirical “try and see” approach is the right strategy; don’t try to detect NAT type a priori and skip checks.
  7. IPv6 candidate ordering. IPv6 candidates often get high priority but route through unfamiliar paths that may be slower than IPv4. Some applications artificially boost IPv4 priority.
  8. NAT mapping timeout. UDP NAT mappings expire after a period of idleness. RFC 4787 REQ-5 mandates that a UDP mapping timer “MUST NOT expire in less than two minutes” and recommends a default of “five minutes or more” — but real-world consumer and especially mobile-carrier NATs routinely violate this and prune aggressively (tens of seconds is not unheard-of on cellular). Treat the spec value as a ceiling for what you can rely on, not a floor. For long-lived idle connections, send keepalive packets to refresh the mapping; RTCP packets (and STUN consent-freshness checks per RFC 7675) serve this purpose in WebRTC.
  9. TURN over UDP blocked, falling back to TURN over TCP. Some firewalls block UDP entirely. TURN-over-TCP works but each “datagram” becomes a TCP message, introducing head-of-line blocking and TCP retransmit semantics that real-time media tolerates poorly.
  10. The IPv4 exhaustion / CGN trend. Carrier-grade NAT is making NAT traversal harder over time on mobile networks, not easier. Multiple layers of NAT (CGN + customer-premises NAT) compound the difficulty. Some apps use direct relay even when STUN appears to find a candidate, because hole-punching success rates have eroded.
  11. STUN/TURN server placement. Latency-sensitive: a TURN server in Frankfurt is useless for a North American media call. Multi-region TURN deployment with geo-DNS routing is necessary at scale.
  12. Signaling-channel ordering. Trickle ICE candidates can arrive at the peer before the SDP that contextualizes them. Implementations must buffer candidates until the peer SDP is set.
  13. Mobile carrier NAT sometimes silently drops packets at low traffic levels. A WebRTC connection that goes idle for 60 s on cellular may be killed by the carrier’s NAT pruning. Aggressive keepalives mitigate.

9. Worked Example — Two Users on Different Networks

Setup: Alice is on home WiFi (residential ISP, port-restricted-cone NAT, public IP 1.2.3.4). Bob is on corporate WiFi (symmetric NAT, public IP 5.6.7.8). They open a WebRTC-based video call via a service that has STUN and TURN servers in 10.0.0.1 and 10.0.0.2 respectively.

T = 0 s. Both clients have already connected to the signaling server (WebSocket).

T = 0.05 s. Alice’s RTCPeerConnection starts ICE gathering.

  • Local candidate (host): 192.168.1.10:60000. Trickled immediately.
  • STUN binding request to 10.0.0.1:3478 from local port 60000.

T = 0.10 s. STUN server responds. Alice’s srflx candidate: 1.2.3.4:60000. Trickled.

T = 0.20 s. Alice’s TURN allocation completes. Relay candidate: 10.0.0.2:50000. Trickled.

T = 0.05–0.30 s (in parallel). Bob does the same, but his symmetric NAT means STUN-derived mapping is not useful for direct contact; he allocates TURN. Bob’s candidates: host 10.20.30.40:62000 (corporate LAN private), srflx 5.6.7.8:62000 (will not actually work), relay 10.0.0.2:51000.

T = 0.40 s. Both sides have all candidates. Alice receives Bob’s candidates via signaling.

T = 0.45 s. Alice begins connectivity checks, ordered by priority of pairs:

PairLocalRemotePriorityResult
1Alice hostBob hosthightimes out (different LANs)
2Alice srflxBob srflxhighAlice→Bob fails (Bob’s symmetric NAT)
3Alice srflxBob relaymediumsucceeds! Alice can hit Bob’s TURN-allocated address
4Alice relayBob srflxmediumtimes out (Bob’s symmetric NAT can’t accept inbound on the srflx mapping from Alice’s relay address)
5Alice relayBob relaylowsucceeds (always works through TURN)

T = 0.65 s. Pair 3 (Alice srflx → Bob relay) wins. Bob’s TURN server forwards Alice’s STUN binding to Bob; Bob receives it on his TURN-relayed allocation. Bob’s response goes back through his TURN server to Alice’s srflx address. Reverse path: Bob → Bob’s TURN → Alice’s srflx (works because Alice’s port-restricted NAT now has a mapping for “Bob’s TURN server” since Alice sent the inbound check there).

T = 0.70 s. ICE nominates pair 3. Half-relayed: media from Alice goes directly (to Bob’s TURN server which forwards to Bob); media from Bob goes through his TURN server. The TURN bill is half what a fully-relayed pair would cost.

T = 0.80 s. DTLS handshake over the nominated pair.

T = 0.95 s. SRTP keys derived. Media starts flowing. Total ICE setup time: ~700 ms — typical for the partially-relayed case.

If both sides had host or srflx connectivity, ICE would have completed in ~250 ms with no TURN involvement. If both were behind symmetric NATs (more restrictive than Bob alone), only pair 5 (relay-relay) would have worked, and the operator would pay for full bidirectional relay.

10. Operating a TURN Fleet — Cost and Capacity Planning

For a service designing its own ICE infrastructure rather than buying TURN-as-a-service from Twilio or Cloudflare, the operational concerns are concentrated in the TURN fleet. STUN is essentially free to operate (a single instance handles billions of requests per day). TURN is bandwidth-bounded; capacity planning is therefore bandwidth planning.

The classic coturn server (the canonical open-source TURN implementation, github.com/coturn/coturn, used by Discord, Jitsi, and many others) achieves approximately:

  • 10 000 simultaneous voice flows per coturn instance on a 10 Gbps NIC, where each flow is bidirectional Opus at ~50 kbps = 100 kbps round-trip, totaling 1 Gbps of NIC traffic at saturation.
  • 500 simultaneous video flows per coturn instance on a 10 Gbps NIC, where each flow is ~2 Mbps round-trip = 1 Gbps NIC at saturation.
  • CPU is rarely the bottleneck; most time is spent in recv() / send() calls and packet checksumming. Modern Intel/AMD server CPUs at 3+ GHz with kernel bypass (DPDK, eBPF) can push 10× this throughput, but vanilla coturn doesn’t use kernel bypass.

For a global service, the regional-deployment math is the headline cost driver. Twilio Voice’s published 2023 pricing was roughly 0.70 in raw bandwidth. At 10% TURN-relay rate across millions of calls, this scales rapidly into the millions-of-dollars-per-month range.

Cost optimization techniques operators use:

  • Half-relay over full-relay. ICE prefers candidate pairs where only one side is relayed — half the bandwidth.
  • Regional TURN colocation. Place TURN servers in the major Internet Exchange Points (IXPs) where bandwidth is cheapest.
  • TURN-over-bandwidth-egress-cheap clouds. Hetzner, OVH, Bytemark have egress prices 5–10× cheaper than AWS/GCP. Most TURN fleets at scale do not run on hyperscale clouds for this reason.
  • Aggressive direct-P2P retry on network changes. When a user roams (WiFi → cellular), re-attempt direct connectivity instead of inheriting the previous flow’s TURN-relay.
  • Per-tenant TURN quotas. For multi-tenant SaaS WebRTC providers, enforce per-tenant relay quotas so one customer’s bug (e.g., a TURN-only mobile app version) doesn’t drain the global TURN pool.

The economics push toward minimizing TURN usage at every level: the “no TURN at all” rate is the operator’s profit margin.

11. See Also