STUN, TURN and coturn
Article.
A voice or video call wants a direct path between two devices, and most devices sit behind NAT and firewalls that do not allow one. STUN tells a device what its public address looks like; TURN relays the media when no direct path works; ICE tries every candidate path and keeps the best.
The interview question is why a call that works on office Wi-Fi fails on a mobile network, and what running the TURN server that fixes it costs and risks. coturn is the TURN server almost everyone runs, including next to Matrix homeservers.
The mechanism: what NAT does to an incoming packet
A NAT rewrites an outgoing packet’s source to its public address and a port, and remembers the mapping so replies come back. Whether a different remote host can use that mapping is what decides if a direct call is possible:
| NAT mapping | Same public port for every destination? | Direct path |
|---|---|---|
| Endpoint-independent | yes | usually works with STUN |
| Endpoint-dependent (“symmetric”) | no, a new port per destination | fails; needs TURN |
STUN (RFC 8489) is a one-packet question to a public server: “what address
did my packet come from?” The answer arrives in XOR-MAPPED-ADDRESS. Sending
a Binding Request from one local UDP socket to two STUN servers run by
different providers shows which NAT you are behind:
request = struct.pack("!HHI12s", 0x0001, 0, MAGIC, txn) # Binding Request
sock.sendto(request, server)
data, _ = sock.recvfrom(2048) # Binding Success
port = struct.unpack("!H", val[2:4])[0] ^ (MAGIC >> 16) # XOR-MAPPED-ADDRESSlocal socket port 62431
stun.l.google.com (74.125.250.129) sees us as <public IP>:58498
stun.cloudflare.com (162.159.207.0) sees us as <public IP>:8404
same mapping for both servers: False -> symmetric NAT, expect TURNRun from a home network on 2026-10-15 (public IP masked): one local socket, two different public ports. The address a STUN server reports is useless to a peer, because the NAT will hand that peer yet another port. Carrier-grade NAT on mobile networks behaves the same way, which is why calls that work on office Wi-Fi fail on 4G.
ICE: try everything, keep the best
ICE (RFC 8445) gathers candidates for each side, exchanges them through the signalling channel, and runs connectivity checks between every pair:
- host candidates: the device’s own addresses, for the same LAN;
- server-reflexive: the public address STUN reported;
- relay: an address allocated on a TURN server.
The cheapest working pair wins. Relay is the last resort and the one that always works, because both sides talk out to the TURN server, which NAT always allows.
TURN: a relay you have to pay for
TURN (RFC 8656) allocates a public address on the server and forwards packets between it and the client. The cost is real: every relayed call passes its full media bandwidth through your server, in and out. A 1:1 video call at around 1.5 Mbit/s each way is about 6 Mbit/s of relay traffic, and egress is what cloud providers bill. Size TURN for the share of calls that end up relayed, typically a minority but never zero.
Credentials that expire
A TURN server must not be open to anyone, but clients cannot hold a permanent password. The TURN REST scheme solves it with a secret shared between the homeserver and coturn: the server mints a username that embeds an expiry time, and the password is an HMAC that coturn can recompute:
secret = b"shared-with-coturn" # turn_shared_secret = static-auth-secret
username = f"{int(time.time()) + 3600}:@alice:example.org"
digest = hmac.new(secret, username.encode(), hashlib.sha1).digest()
password = base64.b64encode(digest)coturn checks the HMAC and rejects the username once its timestamp has
passed. No database of TURN users, nothing to revoke: a leaked credential dies
on its own. Synapse hands these out through GET /_matrix/client/v3/voip/turnServer
when turn_shared_secret is set (VoIP calls on Matrix).
A coturn configuration that holds up
# /etc/turnserver.conf
use-auth-secret
static-auth-secret=<64 random characters, shared with the homeserver>
realm=turn.example.org
listening-port=3478 # STUN/TURN over UDP and TCP
tls-listening-port=5349 # TURN over TLS; 443 gets through strict firewalls
cert=/etc/coturn/fullchain.pem
pkey=/etc/coturn/privkey.pem
min-port=49152 # the relay range, open for UDP
max-port=65535
external-ip=203.0.113.10 # when coturn itself is behind NAT, e.g. on EC2
no-tcp-relay # media is UDP
no-multicast-peers
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=169.254.0.0-169.254.255.255 # includes the cloud metadata service
user-quota=12 # streams per user: about three relayed calls
total-quota=1200TURN over TLS on port 443 is what gets calls out of corporate networks that allow only HTTPS; it needs its own IP or a TLS-aware proxy if a web server already holds 443 there.
Operating it
- Give clients several URIs.
turn:over UDP for most calls,turn:over TCP andturns:on 443 for restrictive networks; ICE uses whichever works. In Synapse that is a list inturn_uris. - Put TURN near the users. A relayed call’s latency is the path through the relay, so one TURN server on another continent adds hundreds of milliseconds to every relayed call. Regional TURN servers, chosen by DNS or by the homeserver per user, keep relayed calls usable.
- Keep it off the homeserver host. Relay traffic is bursty bandwidth and a wide open UDP port range; a separate host keeps a call spike from starving the homeserver and keeps the port range out of its security group.
- Measure what is relayed. coturn’s Prometheus exporter (the
prometheusoption, port 9641 by default, off by default and missing from some distro builds) reports allocations and traffic; the number that matters is the share of calls that needed the relay and their bandwidth. Leave per-username labels off: with time-limited credentials every user is a new label.
Where it breaks in production
-
TURN as a door into your network. A TURN client asks the server to relay to any IP and port it names. Without
denied-peer-iprules for private ranges, loopback and169.254.169.254, an authenticated user can reach internal services through the relay: server-side request forgery over UDP and TCP.The rules themselves have been bypassed: three coturn CVEs in 2026 slipped past them with IPv4-mapped IPv6 addresses (
::ffff:127.0.0.1) and IPv6 range matching, fixed in releases up to 4.16.0. Run a current coturn and test the deny list with IPv6 forms. -
Calls connect, then no audio. The relay port range is closed in the security group or the firewall, or
external-ipis missing so coturn advertises its private address. Signalling works, media does not. -
Only some users fail. The ones on symmetric NAT or strict corporate networks, because TURN is missing or reachable only on UDP 3478. Offer TLS on 443.
-
The bandwidth bill. Relayed video at scale is egress cost; watch the relayed share of calls and per-user quotas.
-
A certificate the clients reject. Synapse’s coturn guide notes that some WebRTC clients have rejected certain certificate chains on
turns:; test TLS from real mobile clients, not only a browser.
The trade not taken: no TURN server
The tempting shortcut is STUN only, or a public free TURN service. STUN-only calls fail for every user behind symmetric NAT, which on mobile networks is many of them, and the failure looks like “the app’s calls are flaky”. A third party’s TURN server sees every relayed packet’s metadata and can disappear. For a product with calls, running coturn (or paying for a managed TURN service) is part of the cost of the feature, not an optimization.
Related
Interview angle 4
- “STUN vs TURN?” - STUN tells a device its public address and port, one
request, no relaying; TURN relays the media itself when no direct path
works. ICE tries host, STUN-derived and TURN candidates and keeps the best.
- Follow-up: when does STUN alone fail? Behind symmetric NAT, where each destination gets a different public port. Measured: one socket, two STUN servers, ports 58498 and 8404, so the reported address is useless to a peer.
- “How do you stop strangers using your TURN server?” - time-limited
credentials: the homeserver mints
expiry:userand an HMAC with a secret shared with coturn, which recomputes it and rejects expired usernames. - “What is the security risk of running TURN?” - it relays to addresses
the client chooses, so it can reach your private network and the metadata
service. Deny private, loopback and link-local ranges, and keep coturn
current: IPv4-mapped IPv6 tricks bypassed the deny list until recent
releases.
- Follow-up: why open port 443? Strict networks allow only HTTPS; TURN over TLS on 443 gets calls out of them.
- “Calls connect but there is no audio. Where do you look?” - the relay
UDP range in the firewall, and
external-ipwhen coturn runs behind NAT.