When two QuickDesk clients cannot establish a direct WebRTC P2P connection (e.g., both behind symmetric NATs), all media traffic must be relayed through a TURN server. This guide explains how to deploy coturn — the most widely used open-source TURN/STUN implementation.
coturn supports two authentication mechanisms. Choose one — they are mutually exclusive:
| Mode | Config Directives | Use Case |
|---|---|---|
| Long-Term Credentials | lt-cred-mech + user= |
Static users, simple setups |
| REST API / HMAC Temporary Credentials | use-auth-secret + static-auth-secret= |
Integration with QuickDesk signaling server; credentials are time-limited and generated on demand |
For production use with the QuickDesk signaling server, REST API mode is recommended — the signaling server's /v1/ice-config endpoint automatically generates short-lived TURN credentials so no static passwords are exposed to clients.
- CentOS 7/8/Stream, Rocky Linux 8+, or Ubuntu 20.04+
- 1GB+ RAM
- Public IP address (required for TURN relaying)
- Open UDP/TCP ports (see firewall section)
sudo yum install -y epel-release
sudo yum install -y coturnsudo apt update
sudo apt install -y coturnturnserver --versionThe default configuration file is /etc/coturn/turnserver.conf (Debian/Ubuntu) or /etc/turnserver.conf (CentOS).
sudo cp /etc/coturn/turnserver.conf /etc/coturn/turnserver.conf.bak # backup
sudo tee /etc/coturn/turnserver.conf > /dev/null << 'EOF'
# =============================================================================
# coturn Configuration for QuickDesk
# =============================================================================
# Public IP address of this server.
# Replace with your actual server's external/public IP.
# Required for proper TURN relaying behind NAT.
external-ip=115.190.196.189
# If the server has a specific local (internal) NIC IP, specify it here.
# Format: external-ip=<public-ip>/<private-ip>
# Example: external-ip=115.190.196.189/10.0.0.5
# ---------------------------------------------------------------------------
# Network Ports
# ---------------------------------------------------------------------------
# STUN/TURN listening port (default 3478)
listening-port=3478
# TLS listening port (default 5349) — only needed when TLS is enabled
# tls-listening-port=5349
# UDP relay port range for media traffic.
# Each active TURN allocation uses one port from this range.
# Ensure these ports are open in your firewall.
min-port=49152
max-port=65535
# ---------------------------------------------------------------------------
# Identity / Realm
# ---------------------------------------------------------------------------
# Realm is used in TURN authentication challenges.
# Typically set to your domain name.
realm=turn.example.com
# Server name (used in some client handshakes, defaults to realm if unset)
# server-name=turn.example.com
# ---------------------------------------------------------------------------
# TLS (optional but recommended for production)
# ---------------------------------------------------------------------------
# Disable TLS — comment these out if you want TLS
no-tls
no-dtls
# To enable TLS, comment out the two lines above and configure:
# cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
# pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
# Do not print logs to stdout (recommended for systemd-managed services)
no-stdout-log
# Log file path
log-file=/var/log/coturn/turn.log
# Uncomment for detailed debug logging (very verbose, use only for debugging)
# verbose
# Add fingerprint to TURN messages (recommended for compatibility)
fingerprint
# ---------------------------------------------------------------------------
# Authentication — Choose ONE of the two modes below
# ---------------------------------------------------------------------------
# ~~~ MODE 1: Long-Term Static Credentials ~~~
# Use this for simple setups with fixed usernames/passwords.
# Add as many `user=` lines as needed.
#
# lt-cred-mech
# user=alice:password1
# user=bob:password2
# ~~~ MODE 2: REST API / HMAC Temporary Credentials (Recommended) ~~~
# Use this to integrate with the QuickDesk signaling server.
# The signaling server uses this shared secret to generate time-limited
# TURN credentials on behalf of clients via /v1/ice-config.
# Credentials expire after a short window (typically 1 hour), so even if
# intercepted they cannot be reused indefinitely.
#
use-auth-secret
static-auth-secret=your-strong-secret-here
# ---------------------------------------------------------------------------
# Relay Protocol Options
# ---------------------------------------------------------------------------
# Uncomment to disable UDP relay (forces TCP relay only)
# no-udp-relay
# Uncomment to disable TCP relay (forces UDP relay only)
# no-tcp-relay
# Uncomment to act as a STUN-only server (no TURN relaying at all)
# stun-only
EOFSecurity note: Replace
your-strong-secret-herewith a random string of at least 32 characters. Generate one with:openssl rand -hex 32
sudo mkdir -p /var/log/coturn
sudo chown turnserver:turnserver /var/log/coturn # CentOS/Rocky
# or
sudo chown turnserver:turnserver /var/log/coturn # Ubuntu# Enable the service
sudo systemctl enable coturn
sudo systemctl start coturn
# Check status
sudo systemctl status coturn# Enable the service (must also set TURNSERVER_ENABLED=1)
sudo sed -i 's/^#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn
sudo systemctl enable coturn
sudo systemctl start coturn
# Check status
sudo systemctl status coturn# Open STUN/TURN control port (UDP + TCP)
sudo firewall-cmd --permanent --add-port=3478/udp
sudo firewall-cmd --permanent --add-port=3478/tcp
# Open TLS port if TLS is enabled
# sudo firewall-cmd --permanent --add-port=5349/udp
# sudo firewall-cmd --permanent --add-port=5349/tcp
# Open UDP relay port range (must match min-port/max-port in config)
sudo firewall-cmd --permanent --add-port=49152-65535/udp
sudo firewall-cmd --reload
# Verify
sudo firewall-cmd --list-portsFor Ubuntu ufw:
sudo ufw allow 3478/udp
sudo ufw allow 3478/tcp
sudo ufw allow 49152:65535/udp
sudo ufw reloadWhen using REST API mode (use-auth-secret), configure the same static-auth-secret in the signaling server's .env:
# /opt/quickdesk/signaling/.env
TURN_AUTH_SECRET=your-strong-secret-here
TURN_URLS=turn:115.190.196.189:3478
STUN_URLS=stun:115.190.196.189:3478The signaling server will then respond to /v1/ice-config requests with short-lived credentials:
{
"iceServers": [
{
"urls": "turn:115.190.196.189:3478",
"username": "1700000000:user",
"credential": "<hmac-sha1-of-username>"
}
]
}Clients use these temporary credentials — they are valid only for a limited time window (typically 1 hour) even if intercepted.
sudo systemctl status coturn
# Tail logs
sudo tail -f /var/log/coturn/turn.log# Install stun-client or use an online STUN tester
# Quick test with nc (should get a response on UDP 3478)
echo -n "" | nc -u 115.190.196.189 3478 -w 2Use https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ — enter your TURN server URL and credentials to verify allocations succeed.
# Follow live logs
sudo tail -f /var/log/coturn/turn.log
# View systemd journal
sudo journalctl -u coturn -f
# Check for allocation errors
sudo grep -i "error\|fail\|denied" /var/log/coturn/turn.logsudo tee /etc/logrotate.d/coturn > /dev/null << 'EOF'
/var/log/coturn/turn.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
postrotate
/bin/kill -HUP $(cat /var/run/coturn.pid 2>/dev/null) 2>/dev/null || true
endscript
}
EOF- Long-term mode: Check that the
user=credentials in the config exactly match what the client sends. - REST API mode: Ensure
static-auth-secretin coturn and the signaling server are identical. Verify system clocks are synchronized (NTP) — HMAC credentials are time-sensitive.
# Check time synchronization
timedatectl status
sudo systemctl enable --now chronyd # or ntpd- Verify the
min-port/max-portrange is open in the firewall. - Check
external-ipis set to the correct public IP (not a private/internal IP).
# Check config syntax
sudo turnserver -c /etc/coturn/turnserver.conf --check-config
# View startup errors
sudo journalctl -u coturn -n 50 --no-pagersudo lsof -i:3478- Use REST API mode in production — static user passwords never reach clients
- Set
denied-peer-ip— prevents SSRF attacks targeting internal services - Enable TLS (
no-tls/no-dtlsremoved, certificate configured) for encrypted TURN traffic - Rotate
static-auth-secretperiodically — update both coturn and the signaling server - Firewall the relay port range — only open UDP 49152-65535, not all ports
- Monitor allocations — excessive allocations may indicate abuse