Managed TURN via Cloudflare (recommended)
Cloudflare’s Realtime TURN service runs on Cloudflare’s global anycast network. It needs no public IP, no UDP port range, and no TLS certificates on your side, and it includes a generous free tier.1
Create a TURN key
In the Cloudflare dashboard, open Realtime > TURN Server and create a key. Copy the Turn Token ID and its API token (the token is shown only once).
2
Set the server environment
In The signaling server mints short-lived ICE credentials from Cloudflare and caches them. These variables take precedence over the coturn variables below, so you do not need to run coturn.
.env:.env
Self-hosting with coturn
Prefer to run the relay yourself? Leave the Cloudflare variables unset and configure coturn instead. TURN requires a public IP address, a domain name, and TLS certificates. The bundledcoturn service uses host networking and is intended for a Linux host with a public IP.
The coturn service uses host networking (network_mode: host). This only works on Linux. On macOS or Windows with Docker Desktop, you will need to run coturn separately or on a Linux VM.
Setup
1
Create the coturn config
Terminal
coturn/turnserver.conf and set:static-auth-secretto a strong random value (e.g.openssl rand -hex 32)realmto your TURN hostname (e.g.turn.your-domain.com)certandpkey(both commented out) to the paths of your TLS certificate and keyexternal-ipto the machine’s public IP, if the interface itself holds a private address
external-ip is commented out in the example file, and uncommenting it is the normal case on AWS, GCP, Azure, and any VPS behind 1:1 NAT. Leave it commented out only when the public address is bound directly to the interface. Get it wrong and coturn advertises a relay candidate nobody can route to, so relayed transfers fail with no error message.The certificate mount is shipped commented out too. Uncomment it in docker-compose.yml so the paths you just set in turnserver.conf exist inside the container:docker-compose.yml
curl quickstart rather than a clone, that means editing your downloaded docker-compose.yml. Skip it and the signaling server still advertises a turns: URL, pointing at a port that cannot complete a TLS handshake.2
Match the server environment
In The signaling server uses these to issue time-limited HMAC-SHA1 credentials for coturn. Credentials expire after 24 hours.
.env:.env
3
Open firewall ports
On the host, open the following ports:
These ports must be reachable directly. TURN is not HTTP, so it cannot be routed through the reverse proxy that fronts the web client and signaling server. Even on a one-domain deployment, coturn keeps its own ports and its own TLS certificate.
4
Start the stack with the TURN profile
Terminal
coturn/turnserver.conf first, as the earlier step describes. Starting the profile without it looks successful and is not.Verify
Check that the signaling server returns TURN credentials:Terminal
turn: and turns: entries, plus a stun: entry pointing at your TURN host:
Output
urls is an array rather than a string: one stun:stun.cloudflare.com:3478 with no username or credential, and one carrying both a turn: and a turns: URL with the credentials. See the API reference for the exact shape. When neither the Cloudflare variables nor TURN_SECRET / TURN_DOMAIN are set, the endpoint returns the Google public STUN servers only.
Credentials are minted from Cloudflare and then cached in memory for 5 minutes. Two consequences worth planning around: both keys are read once at startup, so a rotated key needs a restart to take effect (the restart starts with an empty cache, so the new key is used at once); and if the Cloudflare API starts failing, including after the old key is deleted, the server keeps serving the last good copy for up to about 23 hours after it was minted (logging Cloudflare TURN mint failed at most once every 5 minutes) before it falls back, so a broken key can look healthy for a while.
How credentials work
The signaling server generates time-limited credentials using HMAC-SHA1. The username is{expiry_unix_timestamp}:floeuser and the password is base64(HMAC-SHA1(TURN_SECRET, username)). coturn validates these against the shared secret without needing a database of user accounts.
Relayed transfers stay capped at 2 GB per session even on a relay you run yourself. The cap is compiled into the browser client, the CLI, and the desktop app, not read from your server, so raising it means building all three from modified source. Direct connections are unaffected and have no size limit. See File size limits.