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

> 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.

Source: https://jitsi.help/guides/jitsi-turn-server-coturn/
Updated: October 5, 2026
Publisher: Jitsi Help (https://jitsi.help/)

## 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:

   ```bash
   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:

   ```ini
   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:

   ```bash
   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:**

   ```bash
   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:

   ```ini
   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:**

   ```bash
   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`:

   ```bash
   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:**

   ```bash
   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:

```bash
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`):

```bash
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):

```bash
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

- [Jitsi handbook, Setting up TURN](https://jitsi.github.io/handbook/docs/devops-guide/turn), last changed 2024-09-23, official doc
- [jitsi-meet doc/debian/jitsi-meet-turn/turnserver.conf](https://github.com/jitsi/jitsi-meet/blob/master/doc/debian/jitsi-meet-turn/turnserver.conf), checked 2026-10-05, source code
- [jitsi-meet doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example](https://github.com/jitsi/jitsi-meet/blob/master/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example), checked 2026-10-05, source code
- [jitsi-meet debian/jitsi-meet-turnserver.postinst](https://github.com/jitsi/jitsi-meet/blob/master/debian/jitsi-meet-turnserver.postinst), checked 2026-10-05, source code
- [jitsi-meet resources/coturn-le-update.sh](https://github.com/jitsi/jitsi-meet/blob/master/resources/coturn-le-update.sh), checked 2026-10-05, source code
- [jitsi-meet config.js (p2p, useTurnUdp, forceTurnRelay)](https://github.com/jitsi/jitsi-meet/blob/master/config.js), checked 2026-10-05, source code
- [docker-jitsi-meet prosody/rootfs/defaults/prosody.cfg.lua at stable-11248](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/prosody/rootfs/defaults/prosody.cfg.lua), source code
- [lib-jitsi-meet modules/xmpp/strophe.jingle.js (getStunAndTurnCredentials, TURN filter)](https://github.com/jitsi/lib-jitsi-meet/blob/master/modules/xmpp/strophe.jingle.js), checked 2026-10-05, source code
- [docker-jitsi-meet docker-compose.yml and env.example at stable-11248](https://github.com/jitsi/docker-jitsi-meet/tree/stable-11248), 2026-09-14, source code
- [Prosody documentation, mod_external_services](https://prosody.im/doc/modules/mod_external_services), checked 2026-10-05, official doc (Prosody)
- [draft-uberti-behave-turn-rest-00, A REST API for access to TURN services](https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00), standards draft
- [coturn examples/etc/turnserver.conf](https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf), coturn 4.18.0 released 2026-09-08, official doc (coturn)
- [docker-jitsi-meet issue #2324 P2P does not work outside of LAN by default](https://github.com/jitsi/docker-jitsi-meet/issues/2324), opened 2026-09-11, open on 2026-10-05, maintainer comment (saghul) and community report
- [Jitsi handbook, Self-Hosting Guide: Debian/Ubuntu server (firewall ports, TURN note)](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart), official doc
- [Jitsi handbook, Self-Hosting Guide: Docker (TURN server configuration table, gen-passwords.sh)](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker), source last changed 2026-10-02, official doc
- [coturn Docker image README](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md), checked 2026-10-05, official doc (coturn)
- [jitsi-meet PR #17453 chore(prosody): Remove deprecated modules (removes mod_turncredentials.lua)](https://github.com/jitsi/jitsi-meet/pull/17453), merged 2026-06-03, maintainer PR
- [jitsi-videobridge issue #619 Using TCP instead of UDP](https://github.com/jitsi/jitsi-videobridge/issues/619), maintainer comments (damencho, 2021-05-06 and 2024-10-16)
- [jitsi-videobridge commit 89dde6ea7 feat: Remove support for ICE/TCP (#2194)](https://github.com/jitsi/jitsi-videobridge/pull/2194), 2024-07-18, source code
- [docker-jitsi-meet issues #2149 and #2123](https://github.com/jitsi/docker-jitsi-meet/issues/2149), and https://github.com/jitsi/docker-jitsi-meet/issues/2123, 2025, maintainer comments (damencho, aaronkvanmeerten)
- [W3C WebRTC 1.0, RTCIceCandidateType enum (host, srflx, prflx, relay)](https://www.w3.org/TR/webrtc/#rtcicecandidatetype-enum), official spec
- [WebRTC samples, Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/), checked 2026-10-05, official tool (WebRTC project)
- [RFC 8656, TURN: Relay Extensions to STUN, section 1](https://www.rfc-editor.org/rfc/rfc8656), 2020-02, standard
- [jitsi-meet issue #15943 Service missing recommended 'transport' field](https://github.com/jitsi/jitsi-meet/issues/15943), 2025-04-17, maintainer comments (saghul, damencho)
- [docker-jitsi-meet web/rootfs/defaults/settings-config.js at stable-11248](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/web/rootfs/defaults/settings-config.js), source code
- [PiStack, Complete Guide to Self-Hosted Jitsi Meet 2026](https://www.pistack.xyz/posts/self-hosted-video-conferencing-jitsi-guide/), 2026, community report (contains outdated variables)
- [Jitsi stable apt repository Packages index (jitsi-meet 2.0.11248-1 Recommends jitsi-meet-turnserver, Pre-Depends jitsi-videobridge2 2.3-318-gbf271b11f-1)](https://download.jitsi.org/stable/Packages), last modified 2026-09-14, official package metadata
- [coturn examples/etc/coturn.service](https://github.com/coturn/coturn/blob/master/examples/etc/coturn.service), source code
- [docker-jitsi-meet web/rootfs/etc/s6-overlay/scripts/config at stable-11248 (acme-certs paths)](https://github.com/jitsi/docker-jitsi-meet/blob/stable-11248/web/rootfs/etc/s6-overlay/scripts/config), source code
- [docker-jitsi-meet issue #2224 service-unavailable error for extdisco:2](https://github.com/jitsi/docker-jitsi-meet/issues/2224), 2026-02-17, maintainer comment (saghul)
- [coturn src/apps/relay/mainrelay.c (option table, "Bad configuration format")](https://github.com/coturn/coturn/blob/master/src/apps/relay/mainrelay.c), checked 2026-10-05, source code
- [jitsi-videobridge issue #2427 RTP sent to a non-nominated ICE pair](https://github.com/jitsi/jitsi-videobridge/issues/2427), fixed by PR #2441 (2026-09-03), maintainer comment (JonathanLennox)
- [Jitsi handbook, Docker guide, Jitsi Meet configuration (custom-config.js appended to config.js) and rendered config under /run](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker), official doc
- [Big Iron, Self-hosted conference bridge in 2026](https://www.bigiron.cc/guides/self-hosted-conference-bridge-jitsi-meet-vs-galene-vs-bigbluebutton-revisited), 2026-06-11, community report (background only: TURN over TCP 443 is a slower fallback, fix UDP first)

## Need a hand?

If some of your users still cannot connect, [contact our engineers](/contact/?topic=troubleshooting) 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](/services/jitsi-support/) 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.


---

Jitsi Help is an independent service. It is not affiliated with, endorsed by or sponsored by 8x8, Inc. or the Jitsi project. Jitsi and Jitsi Meet are trademarks of 8x8, Inc., used here only to describe the software we host and support.
