Configuring Coturn for Production TURN Relay
Deploying a reliable relay requires isolating media traffic from control planes. Integrate this setup within your broader WebRTC Protocol Stack & Signaling Servers architecture to prevent signaling bottlenecks.
Production turnserver.conf Baseline & External IP Mapping
Disable default STUN fallbacks immediately. Explicitly bind to your public interface using listening-ip=0.0.0.0. The external-ip parameter is non-negotiable.
It maps your private instance IP to the public NAT address. Omitting it forces ICE to advertise unroutable RFC1918 addresses. Connections will fail silently behind symmetric NATs.
Enforce standard ports and restrict unnecessary protocols. Set listening-port=3478 and tls-listening-port=5349. Disable TCP relay unless legacy clients mandate fallback.
Apply the following baseline configuration:
listening-ip=0.0.0.0
listening-port=3478
tls-listening-port=5349
external-ip=<PRIVATE_IP>/<PUBLIC_IP>
lt-cred-mech
fingerprint
static-auth-secret=<YOUR_32_BYTE_SECRET>
realm=turn.yourdomain.com
min-port=49152
max-port=65535
no-tcp-relay
no-multicast-peers
verbose
Long-Term Credential Authentication (TURN REST API)
Static credentials are unacceptable in production. They are highly vulnerable to scraping and replay attacks. Enable lt-cred-mech alongside static-auth-secret to generate time-bound HMAC-SHA1 tokens.
This integrates directly with your backend. Clients request short-lived credentials via a secure endpoint before SDP exchange.
Review token generation lifecycles and secret rotation in the TURN Server Configuration & Auth documentation. Always enforce fingerprint for message integrity.
Set max-allocate-lifetime=3600 to purge stale allocations and conserve relay bandwidth.
const crypto = require('crypto');
function generateTurnCredentials(username, secret, ttl = 86400) {
const expiry = Math.floor(Date.now() / 1000) + ttl;
const hmac = crypto.createHmac('sha1', secret);
hmac.update(`${expiry}:${username}`);
return {
username,
credential: hmac.digest('base64'),
ttl: expiry
};
}
Allocation Validation & Log Pattern Analysis
Restart the daemon after applying changes. Trigger a test allocation using turnutils_uclient. Monitor real-time logs to verify successful routing.
Look for the exact success pattern: INFO: session <id>: relayed address <public_ip>:<port> allocated. If you encounter ERROR: ... not allocated, verify firewall rules immediately.
Ensure UDP/TCP traffic flows freely across your min-port and max-port range.
journalctl -u coturn -f | grep -E "(INFO: session .* relayed address .* allocated|ERROR: session .* relayed address .* not allocated)"
Cross-reference the allocated relay IP against your external-ip mapping. Successful allocation confirms ICE fallback readiness.
Common Configuration Mistakes
- Omitting
external-ipbehind cloud NAT, generating unroutable ICE candidates. - Enabling
static-auth-secretwithoutlt-cred-mech, causing immediate auth rejection. - Leaving default port ranges open to
0.0.0.0without firewall restrictions, enabling amplification attacks. - Forgetting
fingerprint, which disables integrity checks and breaks strict WebRTC clients.
Frequently Asked Questions
Why does my Coturn server allocate relay addresses but WebRTC peers still fail to connect?
This indicates a misconfigured external-ip parameter. ICE is advertising private IPs instead of your public NAT address. Verify the allocated relay IP matches your public interface and confirm UDP/TCP traffic is unblocked on your port range.
Can I use Coturn without a signaling server? No. Coturn exclusively handles media relay allocation and ICE candidate exchange. You require a separate signaling server to exchange SDP offers/answers and coordinate initial ICE negotiation.
How do I rotate the static-auth-secret without dropping active sessions?
Coturn supports multiple static-auth-secret entries. Append the new secret alongside the legacy value, deploy the configuration, and wait for the old secretβs TTL to expire. Remove the legacy entry afterward to achieve zero-downtime rotation.