STUN and TURN are protocols that allow WebRTC clients behind NATs (Network Address Translators) to discover their public IP address and port, and to relay media traffic when direct peer-to-peer connections aren’t possible. ICE (Interactive Connectivity Establishment) is the framework that orchestrates STUN and TURN to find the best possible path for communication.
Let’s see this in action. Imagine two browsers, Alice and Bob, trying to establish a video call.
Alice’s browser, running on a laptop behind her home router, initiates the process. It needs to know its public IP and port.
// Alice's browser code
const peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// Send Alice's ICE candidate to Bob
console.log('Alice ICE candidate:', event.candidate);
sendToBob(event.candidate);
}
};
peerConnection.createOffer()
.then(offer => peerConnection.setLocalDescription(offer))
.then(() => {
// Send Alice's SDP offer to Bob
sendToBob(peerConnection.localDescription);
});
When RTCPeerConnection is initialized with a STUN server, it starts sending STUN "binding request" packets to stun.l.google.com:19302. The STUN server receives this request, sees the source IP and port it arrived on (which is Alice’s public IP and port as seen by the STUN server), and sends back a "binding response." This response contains the discovered public IP and port. Alice’s browser now knows this information.
Bob’s browser does the same. If both are on public IPs, they’ll exchange these discovered public IP/port pairs via their SDP offer/answer exchange. ICE then tries to connect directly using these pairs.
What happens when Alice is behind a restrictive NAT (like a symmetric NAT) or a firewall that blocks UDP? The STUN server might report an IP/port, but direct UDP packets sent from Alice to Bob’s discovered public IP/port might not make it through. This is where TURN comes in.
If the initial ICE candidate gathering and connectivity checks fail, the RTCPeerConnection will indicate that it needs a TURN server.
// Alice's browser code (continued)
const peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your.turn.server.com:3478',
username: 'alice',
credential: 'secretpassword'
}
]
});
When a TURN server is configured, ICE will first try STUN. If STUN alone doesn’t yield a usable connection, ICE will attempt to use the TURN server. A TURN server acts as a relay. Alice’s browser sends her media traffic to the TURN server, which then forwards it to Bob. Similarly, Bob sends his media to the TURN server, which forwards it to Alice. This is less efficient due to the extra hop, but it guarantees connectivity. The username and credential are for authenticating Alice to the TURN server.
The ICE framework tries to find the best path by gathering all possible "candidates." These include:
- Host candidates: Direct IP addresses on the local machine.
- Server reflexive candidates: The public IP/port discovered via STUN.
- Relayed candidates: IP/ports assigned by the TURN server.
ICE then performs a series of connectivity checks between all pairs of candidates from Alice and Bob. It prioritizes these checks based on a complex algorithm that favors local connections, then direct UDP, then relayed connections. The first successful path is chosen for media.
The most surprising thing about how ICE works is that even when you specify a TURN server, it doesn’t immediately default to using it for media. It will always try to establish a direct peer-to-peer connection using STUN-discovered addresses first. Only when direct communication fails will it fall back to relaying through the TURN server. This is a crucial optimization to minimize latency and bandwidth usage.
The exact levers you control are primarily within the iceServers configuration. The order matters. If you list a TURN server before a STUN server, ICE will still attempt STUN first. The protocol type (stun:, turn:) and the specific server addresses are critical. You also control the authentication credentials for TURN servers.
The next concept you’ll run into is the RTCPeerConnectionState and how to monitor it for connection failures, and the iceConnectionState which gives you granular insight into the ICE gathering and connection process.