NAT Traversal Challenges in WebRTC

Every hard WebRTC connectivity problem is a NAT problem wearing a disguise. The protocol layer is well specified and the browsers implement it competently; what varies wildly — and what your users are actually sitting behind — is a router someone bought in 2019, a corporate firewall with a policy nobody remembers writing, and a mobile carrier translating a hundred thousand subscribers onto one address block.

This article covers what NAT does to a peer-to-peer connection, the classification model that actually describes real devices (which is not the one most articles use), why symmetric NAT is the case that costs you money, and what to do about each.

Why the problem exists at all

IPv4 has about 4.3 billion addresses and ran out of unallocated ones years ago. NAT is the workaround that kept the internet functioning: a router holds one public address, devices behind it use private addresses from RFC 1918 ranges, and the router rewrites the source address and port on every outbound packet while keeping a table of translations so replies find their way back.

For a client-server world this is invisible. You initiate, the server responds, the mapping exists for as long as the conversation does. The model quietly assumes one side always initiates and the other side is publicly reachable.

Peer-to-peer violates that assumption on both counts. Neither browser has a public address. Neither can be contacted by an address it never advertised. Both have to initiate outbound so a mapping exists, and both have to do so toward an address they have not learned yet. Everything ICE, STUN, and TURN do exists to work around this one structural problem.

The classification model worth learning

NAT types from most permissive to strictest, and STUN success Full cone, restricted cone, and port-restricted cone NATs all keep a stable external mapping, so STUN succeeds and a direct connection is usually possible. Symmetric NAT assigns a new mapping for every destination, so the address STUN reports is useless to a peer and a TURN relay is required. more permissive stricter Full Cone STUN ✓ any external host Restricted Cone STUN ✓ same IP must be contacted first Port-Restricted Cone STUN ✓ same IP + port required Symmetric STUN ✗ new mapping per dest → needs TURN
Figure 1 — The four common NAT types. The first three keep a stable mapping STUN can report; symmetric NAT does not, which is why a call with symmetric NAT on either side usually has to fall back to a relay.

The diagram above shows the familiar four categories — full cone, restricted cone, port-restricted cone, and symmetric. That model is a useful mental shortcut and it is what you will find in most WebRTC writing. It is also a simplification that has been superseded, and knowing why makes real captures far easier to read.

RFC 4787 replaced the cone taxonomy because it conflates two independent behaviours. A NAT decides, separately:

Mapping behaviour — which external port does an outbound packet get?

BehaviourRuleConsequence for WebRTC
Endpoint-IndependentSame internal port always maps to the same external port, whatever the destinationSTUN works. The address you learn is the address your peer can use.
Address-DependentA new mapping per destination IPSTUN's answer is valid only for the STUN server. Direct connection unlikely.
Address-and-Port-DependentA new mapping per destination IP and portThis is "symmetric." STUN's answer is useless to a peer. Relay required.

Filtering behaviour — whose packets are allowed back in?

BehaviourRule
Endpoint-IndependentOnce a mapping exists, anyone may send to it
Address-DependentOnly hosts you have sent to may reply, from any port
Address-and-Port-DependentOnly the exact address and port you sent to may reply

The old names are just combinations of these two axes: full cone is endpoint-independent mapping with endpoint-independent filtering; port-restricted cone is endpoint-independent mapping with address-and-port-dependent filtering; symmetric is address-and-port-dependent mapping.

The reason to hold the two axes apart is that only the mapping axis determines whether STUN can help you. Filtering determines whether hole punching needs both sides to send first — which ICE does anyway, since its connectivity checks are bidirectional. So a NAT with the strictest possible filtering is fine as long as its mapping is endpoint-independent, while a NAT with permissive filtering and address-and-port-dependent mapping is hopeless. Collapsing both into one four-item scale hides that.

RFC 4787 does require endpoint-independent mapping (REQ-1), and consumer routers have largely complied over the last decade. The devices that have not are disproportionately the enterprise and carrier equipment your business users sit behind.

Why symmetric NAT specifically breaks things

Walk through the sequence. Your device asks a STUN server for its public address. The NAT, being address-and-port-dependent, creates a mapping specifically for the path to that STUN server — say public port 51000 — and the STUN server truthfully reports 203.0.113.7:51000.

You send that to your peer via signaling. Your peer sends a connectivity check to 203.0.113.7:51000. Your NAT looks at the packet, finds no mapping for this source, and drops it — the mapping for port 51000 belongs to the conversation with the STUN server, not this one.

Meanwhile your outbound check to your peer creates yet another mapping, port 51001, which your peer has never heard of. Both sides are sending to addresses that were never valid for each other.

This is the important part: your STUN test passed. You gathered a perfectly good srflx candidate. Everything reports healthy and the call still cannot connect. There is no configuration that fixes it, because the address genuinely does not exist for anyone but the STUN server. The only answer is a TURN relay — a public address that both peers reach outbound, so both NATs create mappings toward it and neither has to accept an unsolicited packet.

Symmetric NAT on either side is enough to force the relay. This is where relay bandwidth cost comes from, and why the relay ratio is a property of your audience rather than of your code.

Hole punching, and the timing nobody mentions

When mapping is endpoint-independent, two peers can connect directly even though neither is reachable, by both sending outbound at roughly the same time:

  1. Both learn their srflx addresses via STUN and exchange them through signaling.
  2. A sends to B's public address. A's NAT creates a mapping. B's NAT drops the packet — B has not sent to A yet.
  3. B sends to A's public address. B's NAT creates a mapping. A's NAT now has a mapping for B and lets it through.
  4. A's next packet is accepted by B for the same reason. The path is open in both directions.

Step 2 failing is expected and correct. It routinely gets misread as an error in logs — a dropped first packet is the mechanism working, not breaking.

Two operational details follow. NAT mappings expire: RFC 4787 requires UDP mapping timers of at least two minutes and recommends five, but real devices are less generous, which is why ICE keeps sending consent-freshness checks for the entire life of a call. And a mapping that expires mid-call produces a connection that dies for no visible reason on a network that is otherwise fine.

Carrier-grade NAT: the same problem, one layer deeper

Mobile networks and a growing number of fixed ISPs cannot give subscribers a public address at all. Instead they run a second NAT in their own infrastructure: your phone gets an address from the shared range 100.64.0.0/10 (RFC 6598), the home router or handset NATs to that, and the carrier NATs again to a public address shared among many subscribers. This is NAT444 — two translations between you and the internet.

What it changes in practice:

  • You control neither layer. No port forwarding, no UPnP, no configuration. Whatever the carrier's equipment does is what happens.
  • Port budgets are finite. Carriers allocate a limited port block per subscriber. Under pressure, mappings are recycled aggressively and time out faster than the RFC recommends.
  • Symmetric behaviour is common. Carrier NAT frequently exhibits address-and-port-dependent mapping, which is why mobile-heavy audiences show markedly higher relay ratios than desktop ones.
  • Handovers reshuffle everything. Wi-Fi to LTE, or one cell tower to another, can change the public address mid-call. This is an ICE restart situation, not a reconnect-from-scratch one.

If your product has significant mobile usage, budget for relay accordingly and read WebRTC on mobile devices for the rest of the mobile-specific picture.

Hairpinning: the failure that only happens in the office

Two peers on the same LAN discover each other's srflx addresses — both the same public IP — and send packets to it. The traffic goes out to the router and has to be turned around and delivered back inside. That turn is called hairpinning, and RFC 4787 requires support for it (REQ-9). Plenty of equipment does not implement it correctly.

The signature is memorable: a call between two machines sitting next to each other fails, while a call to someone across the country works. ICE usually rescues this because host candidates are higher priority and the two machines can reach each other's LAN addresses directly — but mDNS candidate obfuscation and client isolation on guest Wi-Fi can both remove that escape route, and then the relay is the only path left. Two peers in the same room, relaying through a server in another country, is a real and correct outcome.

Does IPv6 solve this?

Partly, and less than people hope.

IPv6 has enough addresses that translation is unnecessary, so the mapping problem disappears — an IPv6 host has a globally routable address and its ICE host candidate is directly usable. That is a genuine improvement, and dual-stack clients regularly connect over IPv6 without any relay.

What does not disappear is filtering. Home routers and corporate firewalls still block unsolicited inbound IPv6 by default, and correctly so. You still need outbound-first hole punching, still need STUN for reachability confirmation, and still need TURN when a firewall refuses. IPv6 removes a translation layer, not a policy layer.

The remaining catch is that both peers must have IPv6 for it to matter, and connectivity is unevenly deployed. So in practice you run dual-stack, gather both families of candidate, and let ICE sort it out — which it does, at the cost of roughly doubling the candidate pairs to check.

What to actually do about it

The uncomfortable summary is that you cannot fix your users' NATs. What you can do:

  • Always configure TURN. Not as an optimisation — as the thing that makes a meaningful slice of your users able to connect at all. Shipping STUN-only is shipping a product that fails silently for people on symmetric NAT and mobile networks.
  • Offer a TCP and a TLS relay path. Networks that block UDP entirely are not rare in enterprises. turns: on port 443 connects where nothing else will.
  • Measure your relay ratio rather than assuming one. The commonly cited 10–20% is a rule of thumb for consumer audiences; your number depends entirely on who your users are. Read the selected candidate pair from getStats() and report it — the method is in TURN cost optimization.
  • Handle disconnected as recoverable and failed with an ICE restart. Mobile networks generate both routinely.
  • Test from the networks that actually hurt. Your office connection is the least informative place to verify connectivity. Guest Wi-Fi, a corporate VPN, and a phone on cellular tell you far more.

That last point is why this site's tester runs in your browser rather than on a server: a checker in a clean data centre reports on a network none of your users are on. Gathering candidates from the machine in question is the only way to see what that machine's NAT will actually do.

Check Your Own Servers

Wondering whether your STUN setup can punch through restrictive NAT, or whether you'll need a TURN fallback? Run your ICE servers through the tester to see exactly which candidate types they produce.

Open the Free ICE Server Tester →