Explained: STUN Protocol

Codelooru: NAT

You're on a video call. The connection is direct — low latency, no relay, no server in the middle eating your bandwidth. Yet both you and the person you're talking to are behind routers, on private networks, with addresses the internet has never heard of. How did your browser find the other person?

The answer, almost certainly, involved STUN. It ran silently in the background before the call even connected, took a fraction of a second, and you never knew it was there. That's by design. STUN is one of those infrastructure-level protocols that powers enormous amounts of modern real-time communication without ever announcing itself.

This post explains what STUN is, why it exists, exactly how it works, where it fits alongside TURN and ICE, and where you'll encounter it in the real world — including in developer tooling like Coder.


The problem STUN solves

If you read our previous post on NAT, you'll know that most devices on the internet live behind a router performing Network Address Translation. Your laptop has a private IP — something like 192.168.1.42 — that means nothing to the wider internet. When you make an outbound connection, the router replaces your private address with its own public IP and records the mapping in a translation table.

This works perfectly for client-server communication. Your laptop reaches out, the server responds to the router's public IP, the router looks up the translation and delivers it to you. Simple.

But peer-to-peer communication breaks this model entirely. If device A wants to connect directly to device B, it needs to know B's public address. B doesn't know its own public address — it only knows its private one. And even if it did, it doesn't know which port the NAT will assign to its outbound traffic. Without this information, there's no way to establish a direct connection.

STUN solves exactly this: it gives a device a way to discover what it looks like from the outside — its NAT-assigned public IP and port.

Your device private: 192.168.1.42 public: ??? STUN Server public IP, port 3478 Binding Request (who am I to you?) Binding Response: 203.0.113.1:54231 Now knows public address 203.0.113.1:54231

STUN is a simple request-response: ask the server what address it sees your packet coming from. That's your public NAT address.


What STUN actually stands for (and its history)

STUN originally stood for Simple Traversal of UDP through NAT, defined in RFC 3489 in 2003. The original vision was ambitious — STUN would be a complete, self-contained NAT traversal solution. Detect your NAT type, discover your public address, and connect directly. Done.

In practice, it didn't work reliably enough. Many NAT implementations in the wild didn't behave cleanly, and the algorithm for classifying NAT types turned out to be flawed. RFC 3489 was eventually retired and replaced by RFC 5389 in 2008 (itself later superseded by RFC 8489 in 2020), which renamed STUN to Session Traversal Utilities for NAT.

The critical philosophical shift: STUN is no longer presented as a complete solution. It's explicitly a tool — one component used by larger protocols like ICE to handle NAT traversal. This distinction matters when you're trying to understand where STUN fits in a real system.


How STUN works — step by step

STUN is elegantly simple. The entire protocol is built around a single concept: the Binding Request. Here's what happens when a device runs a STUN check:

1. The client sends a Binding Request

The device sends a small UDP packet to a known STUN server on the public internet. The default port is 3478. The packet contains a transaction ID — a random 96-bit number used to match the request with its response. That's essentially all it contains.

2. The STUN server observes the source

The STUN server receives the packet. From its perspective on the public internet, the packet's source address is the device's NAT-translated public IP and port — whatever the router assigned when it let the packet through. The STUN server reads this source address off the IP header.

3. The server sends a Binding Response

The server sends a response back containing the XOR-MAPPED-ADDRESS attribute — the client's public IP and port, XOR'd with a magic cookie value. The XOR encoding is deliberate: some middleboxes on the internet perform deep packet inspection and rewrite IP addresses they find in packet payloads, which would corrupt a plaintext address. XOR encoding disguises it as a non-address value.

4. The client learns its external address

The client receives the response, decodes the XOR, and now knows exactly how it appears to the outside world. It can share this address with peers via a signalling channel, giving them the coordinates needed to attempt a direct connection.

Client NAT Router STUN Server Binding Request (src: 192.168.1.42:5000) NAT rewrites src → 203.0.113.1:54231 reads src: 203.0.113.1:54231 Binding Response (XOR-MAPPED-ADDRESS) delivered to 192.168.1.42:5000 public addr: 203.0.113.1:54231 ✓

The full STUN Binding flow. The NAT transparently rewrites the source address — the STUN server reads it from the IP header and reports it back.

Keep-alive: preventing NAT table expiry

NAT translation table entries expire. If no traffic flows through a mapping for a while, the router drops it — and the external port the device was using becomes invalid. STUN has a second job beyond address discovery: it can serve as a keep-alive mechanism. By periodically sending Binding Requests, a device refreshes its NAT mapping and keeps its external address stable for as long as it needs it.


What STUN tells you about your NAT

Beyond just learning your external address, a device can run multiple STUN checks against servers at different IP addresses and compare the results. If the external port is the same regardless of which server it contacts, the NAT is one of the cone variants — hole punching will likely work. If the external port changes between STUN servers, the NAT is symmetric — hole punching will fail, and a relay (TURN or DERP) is needed.

This is exactly the diagnostic logic used by tools like tailscale netcheck — it runs STUN checks and reports your NAT type along with latency to relay servers in each region.


STUN, TURN, and ICE — how they fit together

STUN is often mentioned alongside TURN and ICE. They're related but distinct, and understanding the relationship between them is key to understanding real-world NAT traversal.

STUN — the address discovery tool

As described above: discovers a device's public NAT address. Lightweight, stateless, cheap to run. Works well when NAT is cooperative. Fails when NAT is symmetric or when UDP is blocked entirely.

TURN — the relay fallback

When STUN-assisted hole punching fails, TURN (Traversal Using Relays around NAT) provides a relay server that both devices can connect to. Unlike STUN, the TURN server stays in the data path for the duration of the session — all traffic flows through it. This adds latency and infrastructure cost, which is why TURN is always the fallback of last resort, never the first choice. TURN is defined in RFC 8656 and is a proper extension of the STUN protocol — a TURN server is also a STUN server.

ICE — the framework that orchestrates everything

ICE (Interactive Connectivity Establishment, RFC 8445) is the full NAT traversal framework used in WebRTC and SIP. It doesn't replace STUN or TURN — it uses both. ICE systematically gathers a list of candidates — possible addresses a device can be reached at — and then tries each combination to find the best working path:

  • Host candidates — the device's own local IPs
  • Server-reflexive candidates — the public address discovered via STUN
  • Relay candidates — addresses on a TURN server

ICE runs connectivity checks on all candidate pairs simultaneously, ranks them by priority, and picks the best one that works. Direct local connection beats STUN-discovered address beats TURN relay. The whole process typically completes in under a second.

ICE Framework Gathers all candidates, runs connectivity checks, picks the best working path Host candidate device's own local IP highest priority Server-reflexive public addr via STUN medium priority Relay candidate address on TURN server last resort ← higher priority · · · · · · · · · · · · lower priority →

ICE orchestrates STUN and TURN. It tries all candidate types simultaneously and selects the highest-priority path that actually works.

STUN TURN ICE
Role Discover public address Relay traffic Orchestrate everything
In the data path? No — setup only Yes — full session No — coordination only
Cost to run Very low — stateless High — carries all traffic Low — logic only
Works with symmetric NAT? No Yes Yes (via TURN fallback)
Defined in RFC 8489 RFC 8656 RFC 8445

Where you'll find STUN in the real world

WebRTC — video and audio calling

STUN is most visible in WebRTC, the browser standard powering Google Meet, Zoom's web client, Discord, and virtually every browser-based video call. When you start a call, your browser silently contacts a STUN server to discover its public address, then hands that address to the other peer via the signalling channel. The whole process runs inside RTCPeerConnection — you configure it once and ICE handles the rest:

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    { urls: "stun:stun1.l.google.com:19302" }
  ]
});

Google operates a free public STUN server at stun.l.google.com:19302 — it's so widely used that it appears in millions of open source projects. Cloudflare also runs one at stun.cloudflare.com:3478. For production applications you'd typically configure your own or use a commercial provider alongside TURN, since STUN alone doesn't handle the symmetric NAT case.

VoIP — SIP-based phone systems

SIP (Session Initiation Protocol), the backbone of internet telephony, has used STUN since its early days. When a SIP phone registers with a PBX or SIP proxy, it often uses STUN to discover its public address and embed it in the SDP (Session Description Protocol) body of its INVITE messages — telling the other party where to send RTP audio packets.

Online gaming

Multiplayer games that need direct peer connections — to minimise latency between players — use STUN to discover addresses for hole punching. Console manufacturers run their own STUN infrastructure (what PlayStation and Xbox call "NAT Type 1/2/3" is essentially a description of how STUN-friendly your router is).

Tailscale and DERP

As covered in our earlier DERP post, Tailscale uses STUN as part of its connectivity stack. When two devices try to establish a direct WireGuard tunnel, they each run STUN checks to discover their external addresses. These addresses are shared via the coordination server for hole punching attempts. If STUN-assisted hole punching fails, the connection falls back to a DERP relay — Tailscale's encrypted, cryptographically-blind relay protocol.

Coder — remote development workspaces

Coder is an open-source platform for running cloud-based development environments — essentially VS Code (or any IDE) connected to a remote workspace. When your local machine connects to a remote Coder workspace, it needs a reliable, low-latency tunnel regardless of what network either side is on. Coder uses Tailscale's networking layer under the hood, which means STUN and DERP are directly in the connection path. When you run coder connect or access a workspace, the client runs STUN checks to attempt a direct peer-to-peer tunnel to the workspace agent. If that succeeds, your editor traffic flows directly with minimal latency. If NAT conditions prevent it, the connection falls through to DERP relay — transparently, without any action needed from you.

In practice: If your Coder workspace connections feel slow or latency is higher than expected, it's often a sign that STUN-assisted direct connections are failing and traffic is going through a relay. Running tailscale netcheck on the machine running the workspace agent will show you the STUN results and DERP latencies — the fastest path to diagnosing the issue.

The STUN message format — a quick look under the hood

STUN messages have a fixed 20-byte header followed by optional attributes. The header contains:

  • A 2-byte message type — encodes the method (Binding) and class (Request, Response, Error)
  • A 2-byte message length — length of everything after the header
  • A 4-byte magic cookie — always the fixed value 0x2112A442, used to distinguish STUN packets from other UDP traffic
  • A 12-byte transaction ID — random, used to match requests with responses

The magic cookie value is what makes STUN packets identifiable in a UDP stream — middleware, firewalls, and multiplexers use it to detect and correctly handle STUN messages without inspecting the full payload.


Limitations of STUN

STUN is powerful within its scope, but it has real limits worth knowing:

  • Symmetric NAT defeats it. If the NAT assigns a different external port for each destination, the address STUN reports is useless for connecting to a peer — because when the peer tries to use it, the NAT assigns a different port. TURN is required in this case.
  • UDP-blocking firewalls defeat it. STUN runs over UDP by default. Some corporate environments block all outbound UDP, making STUN unreachable. STUN over TCP exists but is less reliable for address discovery purposes.
  • It reveals your public IP. By design, STUN tells a server your public IP address and port. This is usually fine, but in privacy-sensitive contexts it's worth being aware of.
  • It's not authentication. STUN has optional authentication mechanisms (short-term and long-term credentials), but a basic STUN server is unauthenticated. Anyone can query it. For most use cases this is fine; for TURN (which relays actual traffic) authentication is mandatory.

Wrapping up

STUN is deceptively simple — a request, a response, and a single piece of information: your public address. But that single piece of information is the key that unlocks direct peer-to-peer communication across almost any network. Without it, every video call, every WebRTC connection, every direct game session would require a relay server in the middle — slower, more expensive, and less private.

The modern understanding of STUN is that it's a tool, not a complete solution. It slots into ICE as the first thing to try, before TURN relay becomes necessary. In overlay networking stacks like Tailscale — and by extension platforms built on top of it like Coder — it's the first step in every connection attempt, quietly running in the background before you've typed a single character of code.

If you haven't already, the previous posts in this series cover NAT and DERP — reading them together gives you the full picture of how modern peer-to-peer connectivity actually works.



×