Video and voice calls in the browser (WebRTC) try to connect participants directly. When both sides sit behind NAT or strict firewalls, as in corporate networks, mobile carriers and hotel Wi-Fi, direct connections fail. STUN helps peers discover their public addresses, and TURN relays the media through a server when nothing else works. coturn is the standard open-source TURN/STUN server, used with Jitsi, Nextcloud Talk, Matrix/Element calls, BigBlueButton and custom WebRTC apps.
STUN vs TURN
| STUN | TURN | |
|---|---|---|
| Does | Tells a client its public IP and port | Relays all media through the server |
| Server load | Tiny | Bandwidth-heavy (every relayed stream passes through) |
| Needed when | Most NAT cases | Symmetric NAT, firewalls blocking UDP, strict networks |
Typically 10–20% of calls need TURN. Without it, those users simply can’t connect.
Requirements
- A VPS with a public IP on its interface, and good bandwidth. Relayed video uses the full stream bitrate in and out. See latency vs bandwidth.
- A domain, e.g.
turn.example.com, pointing to it. - A TLS certificate for TURNS (TURN over TLS).
Install
sudo apt install coturn
sudo sed -i 's/^#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn # on older Debian/Ubuntu packages
Configure /etc/turnserver.conf
listening-port=3478
tls-listening-port=5349
# alt-tls-listening-port=443 # if 443 is free on this server: best for strict firewalls
fingerprint
use-auth-secret
static-auth-secret=<long-random-secret>
realm=turn.example.com
server-name=turn.example.com
cert=/etc/coturn/certs/fullchain.pem
pkey=/etc/coturn/certs/privkey.pem
no-tlsv1
no-tlsv1_1
min-port=49152
max-port=65535
# security: block relaying to private and internal networks
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
total-quota=100
bps-capacity=0
no-cli
log-file=/var/log/turnserver/turn.log
simple-log
use-auth-secret+static-auth-secret: time-limited credentials, generated by your app server (Jitsi, Synapse and Nextcloud all support this “REST API” scheme), instead of static usernames.denied-peer-ip: prevents attackers from using your TURN server to reach internal or private addresses (an SSRF-like abuse). Keep these lines.- The relay port range is used for media.
Certificates: copy them from Certbot with a deploy hook (as with other services), owned by the turnserver user. See Let’s Encrypt.
sudo mkdir -p /var/log/turnserver && sudo chown turnserver: /var/log/turnserver
sudo systemctl enable --now coturn
sudo systemctl status coturn
Firewall
sudo ufw allow 3478/tcp
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp
sudo ufw allow 5349/udp
sudo ufw allow 49152:65535/udp
(Plus 443/tcp if you use alt-tls-listening-port.) See UFW.
Connect your apps
Use the same shared secret in the app:
- Matrix Synapse (
homeserver.yaml):
turn_uris: ["turn:turn.example.com:3478?transport=udp", "turn:turn.example.com:3478?transport=tcp", "turns:turn.example.com:5349?transport=tcp"]
turn_shared_secret: "<long-random-secret>"
turn_user_lifetime: 86400000
See Matrix Synapse.
- Nextcloud Talk: Administration settings → Talk → TURN server
turn.example.com:5349, with the secret, and protocol UDP and TCP. - Jitsi: Prosody’s
turncredentialsmodule, with the coturn host and secret. See Jitsi Meet.
Test
Use the Trickle ICE test page (webrtc.github.io/samples), with time-limited credentials generated from the secret. You should see relay candidates for TURN, and srflx candidates for STUN.
Generate test credentials:
secret='<long-random-secret>'
user="$(($(date +%s) + 3600)):test"
pass=$(echo -n "$user" | openssl dgst -sha1 -hmac "$secret" -binary | base64)
echo "$user / $pass"
Bandwidth and abuse
- Monitor traffic. TURN relays full media streams, and a few busy calls can saturate a link. See vnStat and iftop.
- Keep the secret private, and rotate it if leaked. Open TURN servers get abused as proxies.
- Limit allocations with quotas (
total-quota,user-quota).
Quick reference
| Item | Value |
|---|---|
| STUN/TURN | 3478 udp/tcp |
| TURNS (TLS) | 5349 (or 443) |
| Relay range | 49152–65535/udp |
| Auth | use-auth-secret + static-auth-secret |
| Config | /etc/turnserver.conf |
| Test | Trickle ICE: look for relay candidates |
Related: NAT explained and port forwarding.