When does Jitsi need a TURN server, and how do I set up coturn?

Short answer

Jitsi needs TURN for users whose network blocks UDP or only allows TCP 443, and for peer-to-peer calls between two strict NATs. Debian package installs set up coturn automatically through jitsi-meet-turnserver, but docker-jitsi-meet ships no TURN server, so you run coturn yourself and set TURN_CREDENTIALS, TURNS_HOST and TURNS_PORT in .env. Prosody hands each browser short-lived credentials through mod_external_services, and coturn checks them with use-auth-secret. Test it by looking for a "relay" candidate in Trickle ICE or webrtc-internals.

Who this is for

Your Jitsi server works for most people, but some users get no audio or video. Typical reports: it works at home but not at the office, on hotel Wi-Fi or behind a VPN, or a call works with two people and breaks when a third joins. You want those users to connect through a relay without opening anything on their side.

How it works

Media paths. With two participants, Jitsi tries a direct peer-to-peer (P2P) connection. From three participants, everyone sends media to the videobridge (JVB) on UDP 10000. Browsers find paths with ICE, which gathers three kinds of candidate: host (local address), srflx (public address learned from STUN) and relay (an address on a TURN server).

Who needs TURN. TURN is the fallback when no direct path works:

  • Networks that block outbound UDP, so UDP 10000 is unreachable. The Debian install calls TCP 5349 the fallback “when UDP is blocked”.
  • Networks that only allow TCP 443.
  • P2P calls where both people sit behind NATs with “address-dependent mapping” (often called symmetric NAT), where hole punching generally fails. A Jitsi maintainer put it as “STUN alone is not enough”.

How browsers get credentials. The browser asks Prosody for STUN and TURN servers over XMPP (XEP-0215, urn:xmpp:extdisco:2). Prosody’s mod_external_services answers with short-lived credentials derived from a shared secret (the TURN REST scheme). coturn checks them with use-auth-secret and static-auth-secret. The old mod_turncredentials module was removed in jitsi-meet PR #17453, first shipped in 2.0.11031.

What TURN is used for. P2P connections get every STUN and TURN server. The JVB connection only gets TURN over TCP or TLS. TURN over UDP is filtered out unless useTurnUdp is set, because the bridge is normally reachable on UDP. coturn then forwards the media to the bridge over UDP. A maintainer: “the turnserver should be able to communicate with JVB over UDP on its public address”. The videobridge itself dropped ICE over TCP in PR #2194 (2024-07-18), so TURN is the only TCP path left.

What ships. On Debian, jitsi-meet recommends jitsi-meet-turnserver, which installs coturn and configures it for UDP 3478 and TLS 5349. docker-jitsi-meet does not include a TURN server.

Before you start

  • A public IP for coturn, and a DNS name for it. These steps use meet.example.com for both Jitsi and TURN on ports 3478/5349. To share port 443 with Jitsi on one IP, you need a second name such as turn.meet.example.com.
  • A trusted TLS certificate for the TURN name.
  • Firewall rules: 3478/udp, 5349/tcp, and the relay range, 49152-65535/udp by default.
  • A shared secret, for example openssl rand -hex 32 (the Docker password script uses openssl rand -hex 16 the same way).
  • docker-jitsi-meet stable-11248 or Debian packages 2.0.11248.

Steps

Docker (docker-jitsi-meet): coturn on the same host, ports 3478 and 5349

  1. Install coturn on the host. The Jitsi coturn template is written for the distribution package:

    Terminal
    sudo apt install coturn
    sudo sed -i 's/#TURNSERVER_ENABLED/TURNSERVER_ENABLED/' /etc/default/coturn

    The second line enables the service, the same way the Jitsi installer does. The official coturn/coturn image also works. Its README recommends --network=host because Docker handles large port ranges badly.

  2. Write /etc/turnserver.conf. This follows the Jitsi template with explicit relay ports:

    Config
    use-auth-secret
    static-auth-secret=REPLACE_WITH_SHARED_SECRET
    realm=meet.example.com
    listening-port=3478
    tls-listening-port=5349
    min-port=49152
    max-port=65535
    cert=/etc/coturn/certs/meet.example.com.fullchain.pem
    pkey=/etc/coturn/certs/meet.example.com.privkey.pem
    keep-address-family
    no-multicast-peers
    no-cli
    no-tcp-relay
    no-tcp
    no-dtls
    no-tlsv1
    no-tlsv1_1
    syslog

    Then copy every denied-peer-ip= line from the Jitsi template. Those lines stop the relay from reaching private and reserved networks. If the server is behind 1:1 NAT (for example AWS), add external-ip=<public-ip>/<private-ip>.

  3. Give coturn the certificate. If Jitsi’s web container gets Let’s Encrypt for meet.example.com, the files are fullchain.pem and key.pem in ${CONFIG}/storage/web/acme-certs/meet.example.com/. Copy them in the same way Jitsi’s own hook does:

    Terminal
    sudo mkdir -p /etc/coturn/certs
    sudo cp ~/.jitsi-meet-cfg/storage/web/acme-certs/meet.example.com/fullchain.pem /etc/coturn/certs/meet.example.com.fullchain.pem
    sudo cp ~/.jitsi-meet-cfg/storage/web/acme-certs/meet.example.com/key.pem /etc/coturn/certs/meet.example.com.privkey.pem
    sudo chown turnserver /etc/coturn/certs/*.pem
    sudo chmod 400 /etc/coturn/certs/*.pem
    sudo systemctl restart coturn

    Repeat after each renewal, or schedule it.

  4. Open the ports:

    Terminal
    sudo ufw allow 3478/udp
    sudo ufw allow 5349/tcp
    sudo ufw allow 49152:65535/udp
  5. Point Prosody at coturn in .env. These are the variables the stable-11248 Prosody template reads:

    Config
    TURN_CREDENTIALS=REPLACE_WITH_SHARED_SECRET
    TURNS_HOST=meet.example.com
    TURNS_PORT=5349
    TURN_HOST=meet.example.com
    TURN_PORT=3478
    TURN_TRANSPORT=udp

    Set the ports explicitly. TURN_PORT and TURNS_PORT default to 443, and TURN_TRANSPORT defaults to tcp. TURN_CREDENTIALS must equal coturn’s static-auth-secret.

  6. Recreate Prosody:

    Terminal
    docker compose up -d prosody

Docker: TURN on port 443

Some networks only allow TCP 443. You have two choices:

  • Separate host or second IP (simplest). Run coturn there with tls-listening-port=443, then set TURNS_HOST=turn.meet.example.com and TURNS_PORT=443. Jitsi’s own service advertises meet-jit-si-turnrelay.jitsi.net:443. The upstream coturn service runs as user turnserver. If it cannot bind 443, see Open questions.
  • Same host as Jitsi. One IP can serve both only through SNI multiplexing. The handbook shows this for packages with an nginx stream block that sends turn.meet.example.com to coturn on 5349 and the meet name to the web server. For Docker, the same block would sit in a host nginx in front of the web container. That adaptation is not in the handbook (see Open questions).

Debian/Ubuntu packages

  1. Check that TURN is already set up. jitsi-meet recommends jitsi-meet-turnserver:

    Terminal
    dpkg -l jitsi-meet-turnserver coturn
    head -1 /etc/turnserver.conf
    grep -n -A4 'external_services = {' /etc/prosody/conf.d/meet.example.com.cfg.lua

    The first line of the coturn config should be # jitsi-meet coturn config. Do not modify this line.

  2. Install it if it is missing:

    Terminal
    sudo apt install jitsi-meet-turnserver

    The installer moves any non-Jitsi /etc/turnserver.conf to /etc/turnserver.conf.bak. It fills in your domain and secret, and enables coturn in /etc/default/coturn. If a Jitsi coturn config already exists, it prints turnserver is already configured on this machine. and leaves it.

  3. Open the ports: 3478/udp and 5349/tcp, plus the relay range.

  4. Replace old turncredentials setups. Configs from old installs that still load turncredentials need the external_services block from the current template. A maintainer said: “Do not use turn_external use external_services”.

  5. For port 443 on the same host, follow the handbook: a second DNS name, the nginx stream block, nginx moved to port 4444, and the Prosody turns entry changed to port 443.

Configuration reference

Name Where Default What it does
TURN_CREDENTIALS Docker .env unset Becomes external_service_secret. Must match coturn static-auth-secret
TURN_HOST / TURNS_HOST Docker .env unset TURN hosts, comma separated. Setting either loads external_services
TURN_PORT / TURNS_PORT Docker .env 443 / 443 Ports advertised to clients
TURN_TRANSPORT Docker .env tcp udp, tcp or both, for turn entries
TURN_TTL Docker .env 86400 Credential lifetime in seconds. The handbook table misspells it TURN_TLL
TURN_USERNAME / TURN_PASSWORD Docker .env unset Static credentials instead of a secret
STUN_HOST / STUN_PORT Docker .env unset / 443 Adds a stun entry
P2P_STUN_SERVERS Docker .env unset Sets config.p2p.stunServers in the web config
external_service_secret, external_services Prosody config per template Secret and server list sent to clients
useTurnUdp config.js false Also use TURN/UDP for the JVB connection
forceTurnRelay config.js unset Relay-only ICE, for testing
use-auth-secret, static-auth-secret /etc/turnserver.conf off TURN REST credentials
realm /etc/turnserver.conf unset Realm, set to the domain in the Jitsi template
listening-port / tls-listening-port /etc/turnserver.conf 3478 / 5349 Plain and TLS listeners
min-port / max-port /etc/turnserver.conf 49152 / 65535 UDP relay range
external-ip /etc/turnserver.conf empty Public address when coturn is behind NAT
denied-peer-ip /etc/turnserver.conf none Ranges the relay must not reach
no-tcp-relay, no-multicast-peers, no-cli /etc/turnserver.conf off Hardening used by the Jitsi template

Common mistakes

  • Mixing auth modes. TURN_CREDENTIALS is a shared secret for use-auth-secret. It is not a user=name:password entry for lt-cred-mech. Use one mode: secret plus TURN_CREDENTIALS, or static users plus TURN_USERNAME/TURN_PASSWORD. A 2026 community guide mixes them.
  • Variables that do not exist. ENABLE_TURNS, JVB_TURN_* and ENABLE_TCP_HARVESTER are not in docker-jitsi-meet. A maintainer about JVB_TURN_*: “Those do not exist”.
  • Ports left at their defaults. Without TURNS_PORT, clients are told port 443 while coturn listens on 5349.
  • Only STUN_HOST set. external_services only loads when TURN_HOST or TURNS_HOST is set, so the console shows service-unavailable for get STUN/TURN credentials (extdisco:2). A maintainer called it harmless when you have no TURN server. Issue #2324 (P2P without TURN outside the LAN) is open as of 2026-10-05.
  • Relay blocked from the bridge. If coturn reaches the bridge on a private IP, a denied-peer-ip range such as 10.0.0.0-10.255.255.255 blocks it. Let coturn reach the JVB public address.
  • Untrusted certificate on TURNS. coturn needs a trusted certificate for TLS.
  • Relay range closed in the cloud firewall.
  • Service missing recommended 'transport' field in the Prosody log. Add transport = "udp" to the stun entry. Harmless either way.
  • Bad configuration format: no-loopback-peers or dh2066 from coturn. Current coturn no longer has these options. Loopback peers are blocked by default, so the warning is harmless.
  • One-way audio for VPN users is not always TURN. Clients with two network paths could get one-way audio from a bridge bug. It was fixed in jitsi-videobridge PR #2441, which ships in the 2.0.11248 packages (jvb 2.3-318-gbf271b11f).

Verify

On the TURN host:

Terminal
sudo ss -lntup | grep turnserver
openssl s_client -connect meet.example.com:5349 -servername meet.example.com </dev/null 2>/dev/null | grep 'Verify return code'

Expect listeners on :3478 (udp) and :5349 (tcp), and Verify return code: 0 (ok).

Check that Prosody advertises it (Docker renders config into /run):

Terminal
docker compose exec prosody grep -A8 'external_services = {' /run/prosody/config/prosody.cfg.lua

Test coturn directly. Make a one-hour credential from the secret, in the format coturn documents (timestamp:userid, base64 HMAC):

Terminal
SECRET='REPLACE_WITH_SHARED_SECRET'
U="$(( $(date +%s) + 3600 )):test"
P=$(printf '%s' "$U" | openssl dgst -binary -sha1 -hmac "$SECRET" | base64)
echo "username: $U"; echo "password: $P"

Open the WebRTC Trickle ICE page, add turns:meet.example.com:5349 with that username and password, set IceTransports to relay and gather. The page says a TURN server works “if you can gather a candidate with type relay”.

In a real call, add config.forceTurnRelay = true; to custom-config.js for a test. Join three people and open chrome://webrtc-internals (Firefox: about:webrtc). The active candidate pair’s local candidate should be type relay. Remove the setting afterwards.

If it still fails

  • coturn logs to syslog in the Jitsi template: sudo journalctl -u coturn -f. Watch for allocation and authentication errors while a client connects.
  • Browser console: getting turn credentials failed or is mod_turncredentials or similar installed and configured? means Prosody did not answer extdisco.
  • No relay in Trickle ICE: check the secret matches, the clock is correct (credentials expire), the certificate, and the firewall.
  • relay appears but group calls still fail: coturn cannot reach the JVB over UDP on its public address.
  • A TCP-only load balancer in front of the JVB cannot work. Maintainers expect a public IP and port per bridge.

What we have not confirmed yet

We checked everything above against the Jitsi templates and source, the handbook, coturn and Prosody documentation, and maintainer comments on 2026-10-05. We have not yet tested every variant on a real server. These points are still open:

  • Binding coturn to 443 as the turnserver user (packages) or nobody (Docker image with --network=host) may need CAP_NET_BIND_SERVICE. Neither the coturn docs nor Jitsi docs cover it. Test before recommending a systemd drop-in.
  • Same-host 443 for Docker: the handbook nginx stream block is written for packages. Running it in a host nginx in front of the Docker web container is our adaptation and is untested.
  • Whether guests on a secure domain setup get TURN credentials. On Debian, external_services is enabled on the main VirtualHost only, and the secure domain guide does not mention it.
  • Exact Prosody error when an old config still loads turncredentials on 2.0.11031 or newer.
  • Whether a stateful host firewall needs the full relay range open inbound, given the Debian quickstart does not list it.
  • The handbook 443 section uses /opt/acmesh/.acme.sh/acme.sh, a path from older installers (section last changed 2023). Current installs may differ.
  • The quickstart says turnserver setup is skipped when nginx already uses 443, but the current jitsi-meet-web-config postinst has no TURN logic. Possibly stale.
  • Issue #2324 says P2P_STUN_SERVERS had no effect for the reporter. Not reproduced.
  • Which coturn release first rejected no-loopback-peers and dh2066, and whether Debian 12 and Ubuntu 24.04 coturn builds warn.
  • Whether the Docker stable-11248 jvb image has the same jvb build as the 2.0.11248 package.
  • The Jitsi template denies 203.0.113.0/24, the documentation range used for placeholders here. Real public IPs are unaffected.

Sources

Need a hand?

If some of your users still cannot connect, contact our engineers with a chrome://webrtc-internals dump from an affected user and the output of sudo journalctl -u coturn --since '10 min ago'. Our support plans include TURN setup and testing.

Frequently asked questions

Do I need TURN if my users are at home?

Not if they can reach the JVB on UDP 10000, which is what group calls use. TURN matters for networks that block UDP and for some P2P pairs.

Can coturn run on the same server as Jitsi?

Yes, on 3478 and 5349. Sharing 443 on one IP needs a second DNS name and SNI multiplexing.

Does TURN help group calls, or only one-to-one?

Both. For the bridge connection the browser only uses TURN over TCP or TLS unless `useTurnUdp` is set.

Why did the Debian installer skip TURN?

The quickstart says turnserver setup is skipped when nginx already uses port 443 on the machine.

Can I just use a public STUN server?

STUN only finds public addresses. It cannot relay, so it does not help UDP-blocked users.

Stuck, or would rather not do this by hand?

Deploy it in one click

A private Jitsi server in your own AWS account with SSL, your domain and optional recording, transcription and JWT. Free 15 minute trial.

Start free trial

Talk to a Jitsi engineer

Setup, fixes, branding, recording, scaling. Tell us what is happening and we reply with a plan and a quote.

Get expert help

Related

Recently updated