STUN vs TURN: How WebRTC Gets Through NAT
By SRIFT maintainers · Published
Reviewed by SRIFT maintainers · last reviewed against the SRIFT 4.3.2 source.
Short answer
A STUN server tells a device what public IP address and port its traffic appears to come from, so two peers can try to reach each other directly. A TURN server relays the traffic when no direct path works. STUN is cheap because it answers small requests and carries no data; TURN is expensive because every byte of the session passes through it.
WebRTC uses both through ICE, which gathers host, server-reflexive (STUN) and relay (TURN) candidates, tests them in priority order and picks the best pair that works. A TURN relay forwards DTLS-encrypted packets: it sees IP addresses, timing and volume, not content.
- STUN
- Discovers your public address (RFC 8489)
- TURN
- Relays traffic when direct fails (RFC 8656)
- Default ports
- 3478 UDP/TCP, 5349 TLS/DTLS; often 443 for TLS
- Chooser
- ICE (RFC 8445): direct candidates preferred, relay last
Key takeaways
- STUN only tells a device its public address and port. It carries no session data, so it is cheap to run.
- TURN relays every byte when no direct path works: a 10 GB transfer through TURN is 20 GB of the operator's bandwidth.
- ICE gathers host, server-reflexive (STUN) and relay (TURN) candidates, prefers direct pairs and uses the relay last.
- Without TURN, peers on UDP-blocking networks, or behind address-dependent NAT on both sides, cannot connect; TURN over TLS on port 443 reaches most locked-down networks.
- A TURN server forwards DTLS-encrypted packets: it sees IP addresses, timing and volume, not content.
This guide covers the two server types and how ICE chooses between them. Why NATs block connections, and how hole punching works, is in NAT traversal explained.
The problem STUN and TURN solve
Most devices sit behind network address translation (NAT). A laptop knows its private address, such as 192.168.1.20, but the rest of the internet sees the router's public address and a port the router chose. The NAT also drops unsolicited incoming packets. Two peers behind different NATs therefore cannot simply exchange addresses and connect: the addresses they know are wrong, and the doors are closed.
STUN addresses the first problem and lets the peers attempt to open the doors from the inside (hole punching). TURN is the answer when that attempt fails. The NAT traversal guide covers which NAT behaviours defeat hole punching and why.
STUN: learning your public address
STUN (Session Traversal Utilities for NAT, RFC 8489) is a small request/response protocol. The client sends a Binding request; the server replies with an XOR-MAPPED-ADDRESS attribute containing the source IP and port it saw. The address is XOR-encoded so that middleboxes which rewrite IP addresses inside packets do not corrupt it. That observed address becomes the client's server-reflexive candidate.
The same message format is reused for ICE connectivity checks: peers send Binding requests to each other's candidates, and a successful response proves the pair works in both directions. A check can also reveal a new address the peer did not know about, a peer-reflexive candidate.
Why STUN is cheap, and where it stops working
A STUN server holds no session state and forwards no data, so one server can answer a very large number of clients, and many operators run public ones. Its answer is only useful if the NAT reuses the same public mapping for traffic to other destinations (endpoint-independent mapping). A NAT that assigns a new port per destination (often called a symmetric NAT) gives the STUN server one address and the peer another, so the advertised candidate does not work. Networks that block outbound UDP defeat it entirely.
TURN: the relay of last resort
TURN (Traversal Using Relays around NAT, RFC 8656) extends STUN. An authenticated client asks the server for an allocation: a public address on the TURN server that relays for it. The client then installs permissions for the peer addresses allowed to send to it, and may bind channels to shrink the per-packet header. Per the RFC, an allocation lasts 10 minutes by default, a permission 5 minutes and a channel binding 10 minutes, each refreshed while in use.
The client can reach the TURN server over UDP, TCP, TLS over TCP or DTLS over UDP. By default TURN uses STUN's ports: 3478 for UDP and TCP, 5349 for TLS and DTLS. Many deployments also listen for TLS on port 443, because restrictive firewalls that allow nothing else usually allow outbound HTTPS. From the server to the peer, TURN as specified in RFC 8656 always uses UDP.
Why TURN costs money
Every byte of a relayed session enters and leaves the TURN server, so a 10 GB transfer through TURN is 20 GB of the operator's bandwidth. The RFC itself recommends using TURN only when no direct path can be found, and notes that the server needs a high-bandwidth connection. TURN also adds a hop, so latency depends on where the server is relative to both peers, and throughput is capped by the server's capacity and any per-user limits.
How ICE chooses: candidate types and priorities
ICE (RFC 8445) gathers every candidate each peer has, exchanges them through signaling, pairs them, and runs connectivity checks in order of pair priority. A candidate's priority is 2^24 × type preference + 2^8 × local preference + (256 − component ID), so the type preference dominates:
| Type | Where the address comes from | Recommended type preference |
|---|---|---|
| host | An address on the device's own interface (LAN, VPN, or a public IP if it has one). | 126 |
| prflx (peer-reflexive) | Discovered during connectivity checks, from the source address of a peer's request. | 110 |
| srflx (server-reflexive) | The public address a STUN server reported. | 100 |
| relay | An allocation on a TURN server. | 0 |
Pair priority combines the two candidates' priorities, and the controlling agent nominates a working pair with the USE-CANDIDATE attribute. The effect is that a relay pair is selected only when nothing more direct succeeds. Applications can override that with iceTransportPolicy: "relay", which gathers relay candidates only: slower and more expensive, but it hides each peer's IP address from the other and avoids waiting on checks that are known to fail.
Configuring STUN and TURN in a browser
A page passes its servers to RTCPeerConnection as iceServers. STUN entries need no credentials; TURN entries do. List several STUN operators so one outage does not remove server-reflexive candidates, and give TURN a TLS URL on port 443 for restrictive networks.
const { iceServers } = await fetch("/api/ice").then((r) => r.json())
// iceServers looks like:
// [
// { urls: ["turns:turn.example.com:443?transport=tcp",
// "turn:turn.example.com:3478?transport=udp"],
// username: "1767225600:alice", credential: "q2Vx...base64..." },
// { urls: "stun:stun.example.org:3478" }
// ]
const pc = new RTCPeerConnection({ iceServers })Short-lived credentials instead of a shared password
Anything shipped in a page is public. A static TURN username and password in the JavaScript bundle lets anyone use the relay at the operator's expense. The common alternative, described in the expired Internet-Draft “A REST API For Access To TURN Services” and implemented by coturn's shared-secret mode, has the application server mint credentials on request: the username is an expiry timestamp joined to a user ID with a colon, and the password is the Base64 HMAC of that username under a secret shared only with the TURN server. The TURN server recomputes the HMAC and rejects expired usernames, so a leaked credential stops working on its own.
import crypto from "node:crypto"
export function turnCredential(secret, userId, ttlSeconds = 3600) {
const expiry = Math.floor(Date.now() / 1000) + ttlSeconds
const username = `${expiry}:${userId}`
const credential = crypto.createHmac("sha1", secret).update(username).digest("base64")
return { username, credential }
}Rate-limit the endpoint that hands these out, and keep the lifetime short enough to matter. Hosted TURN providers usually offer their own API that returns credentials in the same iceServers shape.
What a TURN server can and cannot see
In WebRTC, DTLS runs end to end between the two peers, above the TURN layer. The relay forwards DTLS records without holding any key, so it cannot read file contents, chat text or media. What it does see:
- The public IP addresses and ports of both peers, and which peers talked to each other.
- When the session started and ended, and how many bytes went each way, packet by packet.
- The TURN username, which in the scheme above contains an expiry and whatever user ID the application chose.
A TURN operator cannot insert itself into the DTLS session, because the peers check certificate fingerprints that reached them through signaling, not through TURN. The signaling server is the party that could swap those fingerprints. For the full inventory of relay metadata, see what a relay can see.
How SRIFT sets up STUN and TURN
SRIFT's browser client (version 4.3.2) assembles its ICE configuration as follows:
- Public STUN from four operators, gathered in parallel:
stun.cloudflare.com:3478,stun.l.google.com:19302,global.stun.twilio.com:3478andstun.nextcloud.com:443(the last for networks that allow only port 443). - TURN from
GET /v1/ice, which mints short-lived credentials from every configured provider in parallel (Cloudflare Realtime TURN, a coturn server using the HMAC scheme above, Metered, or an operator-supplied list), with a 2.5-second timeout per provider and per-provider caching, then merges the answers. Credentials last 6 hours by default (never less than 5 minutes), andturns:URLs on port 443 are sorted first. No TURN credentials are built into the page. - A UDP probe: if no server-reflexive candidate appears within 1.5 seconds, UDP is treated as unusable. With a TURN server available the connection then uses relay-only ICE; with neither, WebRTC is skipped and the transfer goes straight to SRIFT's end-to-end sealed WebSocket relay.
Frequently asked questions
Do I need a TURN server if I already have STUN?
Only if you need connections to succeed for everyone. STUN alone works when at least one usable direct pair exists; on networks that block UDP or use address-dependent NAT mapping, the session fails without TURN or another relay.
Can I use a free public TURN server?
Public STUN servers are common because they carry almost no traffic. TURN carries all relayed traffic, so free open TURN servers are rare and usually rate-limited; production services run their own or buy TURN from a provider and hand out short-lived credentials.
Does using TURN hide my IP address?
From the other peer, yes, if the application forces relay-only ICE; otherwise host and server-reflexive candidates are still exchanged. The TURN operator always sees your public IP address.
References
- RFC 8489: Session Traversal Utilities for NAT (STUN) (IETF)
- RFC 8656: Traversal Using Relays around NAT (TURN) (IETF)
- RFC 8445: Interactive Connectivity Establishment (ICE) (IETF)
- A REST API For Access To TURN Services (expired Internet-Draft) (IETF Datatracker)
- WebRTC: Real-Time Communication in Browsers (W3C)
- RTCPeerConnection() constructor (iceServers) (MDN)
Try a direct transfer
Open a session in one browser, join it from another device, and send a file. No account, nothing to install on either side.
Related resources
- NAT Traversal Explained for Peer-to-Peer AppsWhy devices behind NAT cannot simply connect, how UDP hole punching works, which NAT behaviours defeat it, and why relays remain necessary.Explainer
- WebRTC Behind Corporate Firewalls and ProxiesWhy WebRTC fails on locked-down networks (UDP blocked, proxies, TLS inspection) and the fallbacks that still work over outbound port 443.Troubleshooting
- Why WebRTC Connections Fail, and How to Fix ThemWhy two browsers sometimes cannot connect directly: ICE failures, blocked UDP, symmetric NAT and missing TURN, with a diagnosis checklist.Troubleshooting
- What a File Transfer Relay Can Actually SeeEven with end-to-end encryption, a relay sees metadata: IP addresses, sizes and timing. A concrete inventory, using SRIFT as the worked example.Explainer
