Integrations / Jitsi Meet

Jitsi Meet

A self-hosted Jitsi Meet needs a TURN server for participants on networks that block UDP or use strict NATs. Without it, those users see frozen video, one-way audio, or cannot join.

There are two ways to connect Jitsi to Turnix:

Option A: shared secret (recommended) Option B: static credentials
How Prosody generates fresh TURN credentials per user from your auth secret You paste one credential from the dashboard into the config
Expiry Handled automatically The credential stops working at its TTL; you must replace it
Use for Production A quick test
Let your AI agent configure it

Run Claude Code (or another agent with shell access) on your Jitsi server, set TURNIX_AUTH_SECRET in that shell, and paste the prompt below. The agent configures Prosody (Option A), checks the config and restarts the services.

Configure Jitsi Meet with Turnix
Configure the self-hosted Jitsi Meet on this server to use Turnix (managed TURN/STUN) through Prosody's mod_external_services with a shared secret.

Context about Turnix:
- STUN server: stun.turnix.io port 3478
- TURN servers: <region>.turnix.io, port 3478 (turn, UDP and TCP) and port 443 (turns, TCP). Ask me which region to use (for example us-west) if I have not said so. The region list is at https://turnix.io/app/turn/server.
- The shared secret is my Turnix project's auth secret, available in the environment variable TURNIX_AUTH_SECRET. Never print it or include it in your summary.
- Docs: https://turnix.io/docs/integrations/jitsi

Do the following:
1. Find the Prosody config for the Jitsi domain (usually /etc/prosody/conf.avail/<domain>.cfg.lua) and make a timestamped backup copy. If TURNIX_AUTH_SECRET is empty, stop and ask me to set it.
2. Check that "external_services" is in modules_enabled of the main VirtualHost; add it if missing.
3. At the top level of the file, replace any existing external_service_secret and external_services settings with:
   external_service_secret = "<value of TURNIX_AUTH_SECRET>";
   external_services = {
     { type = "stun", host = "stun.turnix.io", port = 3478 },
     { type = "turn", host = "<region>.turnix.io", port = 3478, transport = "udp", secret = true, ttl = 86400, algorithm = "turn" },
     { type = "turn", host = "<region>.turnix.io", port = 3478, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" },
     { type = "turns", host = "<region>.turnix.io", port = 443, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
   };
   Show me the diff with the secret masked.
4. Run prosodyctl check config and fix any error you introduced. Then restart: systemctl restart prosody jicofo jitsi-videobridge2.
5. If a local coturn is installed and no longer referenced, tell me, but do not stop or remove it without asking.
6. Tell me how to verify: join a meeting from a network that blocks UDP and look for relay candidates on <region>.turnix.io in chrome://webrtc-internals, or check Sessions in the Turnix dashboard (https://turnix.io/app/turn/sessions).

Before you start#

  • A working self-hosted Jitsi Meet (for example the Debian/Ubuntu packages from the Jitsi handbook), with shell access to the server
  • A Turnix project (the free plan works)

Jitsi clients get their STUN/TURN servers from Prosody's mod_external_services. Point it at Turnix.

1. Get the Turnix values#

  1. Open LTC/Auth Token in the dashboard and click Create (or copy the existing secret).
  2. Open Server and copy the endpoint of an online region close to your users, for example us-west.turnix.io.

2. Configure Prosody#

Edit your Prosody config, usually /etc/prosody/conf.avail/<your-domain>.cfg.lua.

Make sure external_services is listed in modules_enabled of your main VirtualHost (the Debian packages enable it by default). Then replace any existing external_service_secret and external_services settings at the top level of the file with:

lua
external_service_secret = "YOUR_TURNIX_AUTH_SECRET";
external_services = {
    { type = "stun", host = "stun.turnix.io", port = 3478 },
    { type = "turn", host = "us-west.turnix.io", port = 3478, transport = "udp", secret = true, ttl = 86400, algorithm = "turn" },
    { type = "turn", host = "us-west.turnix.io", port = 3478, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" },
    { type = "turns", host = "us-west.turnix.io", port = 443, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
};

ttl is how long each generated credential is valid, in seconds. Prosody generates a new one every time a participant joins.

If your installation still runs a local coturn that you no longer need, remove its entries from external_services and stop the service.

3. Restart and verify#

bash
sudo systemctl restart prosody jicofo jitsi-videobridge2

Join a meeting from a restrictive network (for example a corporate VPN or a guest Wi-Fi that blocks UDP). In chrome://webrtc-internals you should see relay candidates on us-west.turnix.io, and the session appears under Sessions in the Turnix dashboard.

Option B: static credentials (quick test)#

  1. In the Turnix dashboard, open Credentials and create a new credential. Choose a region and a TTL (for a test, a few hours is enough).
  2. In the result, switch to the JitsiMeet tab and copy the JSON array.
  3. Paste it as the STUN/TURN server list in your Jitsi Meet config.js (usually /etc/jitsi/meet/<your-domain>-config.js), under p2p.stunServers:
javascript
p2p: {
    enabled: true,
    stunServers: [
        { urls: "stun:stun.turnix.io:3478" },
        { urls: "turn:us-west.turnix.io:3478?transport=udp", username: "<username>", credential: "<credential>" },
        { urls: "turn:us-west.turnix.io:3478?transport=tcp", username: "<username>", credential: "<credential>" },
        { urls: "turns:us-west.turnix.io:443?transport=tcp", username: "<username>", credential: "<credential>" }
    ]
},
Static credentials expire

The credential stops working at the TTL you chose, and anyone who opens your meeting page can read it from config.js. Use Option A for production.

p2p.stunServers applies to one-to-one calls in peer-to-peer mode. Calls through the videobridge get their TURN servers from Prosody, so Option A covers both.

Troubleshooting#

Problem Fix
No relay candidates Check that the secret matches the dashboard and that mod_external_services is enabled. Run prosodyctl check config.
Worked, then stopped You rotated the auth secret (update Prosody), or the static credential from Option B expired.
Only users on restrictive networks fail Make sure the turns entry on port 443 over TCP is present.

Last updated October 30, 2026