Skip to content

Instantly share code, notes, and snippets.

@loftwah
Last active February 28, 2025 11:54
Show Gist options
  • Save loftwah/8389a9ce57fe0f8139ba773875548eca to your computer and use it in GitHub Desktop.
Save loftwah/8389a9ce57fe0f8139ba773875548eca to your computer and use it in GitHub Desktop.
traefik-jitsi

Annex: TURN Server Configuration for Jitsi Meet Stable-10008

This annex provides a comprehensive guide to configuring and troubleshooting a TURN server for Jitsi Meet stable-10008 when self-hosting behind Carrier-Grade NAT (CGNAT) using Localtunnel. It ensures media traffic (audio/video) works reliably despite network restrictions, aligning with the Docker-based setup in the main walkthrough.


Why TURN is Necessary

  • CGNAT Constraints: Your mobile hotspot uses CGNAT, meaning your server lacks a public IP for direct inbound connections. Jitsi’s Videobridge (JVB) relies on UDP port 10000 for media, which CGNAT blocks.
  • Localtunnel Limitation: Localtunnel tunnels TCP traffic (e.g., HTTP on port 8000), but JVB’s UDP traffic can’t be tunneled this way. A TURN server relays media over TCP or TLS, bridging this gap.
  • Stable-10008: This version of Jitsi Meet supports TURN natively, with no deprecated TCP harvester options (removed in earlier releases), making TURN configuration essential for CGNAT setups.

TURN Server Options

For stable-10008, you can use:

  1. Public TURN Server: Free, pre-configured servers like openrelay.metered.ca for testing.
  2. Self-Hosted Coturn: Deploy your own TURN server on a VPS with a public IP for full control.
  3. Commercial TURN: Services like Metered.ca or Twilio for scalability and reliability (not free).

This annex focuses on a public TURN server for simplicity, with notes on self-hosting.


Configuring TURN in Jitsi Stable-10008

Supported Environment Variables

In docker-jitsi-meet stable-10008, TURN configuration is set via the .env file. The valid variables (per the official repo and env.example) are:

  • TURN_HOST: Hostname of the TURN server (UDP/TCP).
  • TURN_PORT: Port for TURN (default 3478).
  • TURNS_HOST: Hostname for TURN over TLS.
  • TURNS_PORT: Port for TURN over TLS (default 5349 or 443).
  • TURN_CREDENTIALS: Username and password in username:password format.

Note: Variables like ENABLE_TURN or TURN_RELAY_* are not supported and should not be used.

Example Configuration

Using the free openrelay.metered.ca TURN server:

# In .env
TURN_HOST=openrelay.metered.ca
TURN_PORT=80
TURNS_HOST=openrelay.metered.ca
TURNS_PORT=443
TURN_CREDENTIALS=openrelayproject:openrelayprojectsecret
  • Why This Server?: It supports TURN over TCP (port 80) and TLS (port 443), which are compatible with CGNAT and Localtunnel’s TCP-only tunneling.
  • Credentials: Publicly available for testing; replace with your own for production.

Integration with Localtunnel Setup

Update your .env from the main walkthrough:

# Basic Jitsi settings
CONFIG=/home/your-username/.jitsi-meet-cfg
HTTP_PORT=8000
TZ=UTC
DISABLE_HTTPS=1
PUBLIC_URL=https://your-jitsi.localtunnel.me
DOCKER_HOST_ADDRESS=172.18.x.x  # Your WSL IP

# TURN server settings
TURN_HOST=openrelay.metered.ca
TURN_PORT=80
TURNS_HOST=openrelay.metered.ca
TURNS_PORT=443
TURN_CREDENTIALS=openrelayproject:openrelayprojectsecret
  • Restart Jitsi after updating:

    docker-compose down && docker-compose up -d

How TURN Works in Stable-10008

  • Frontend Config: The web container uses these .env variables to populate config.js with TURN server details, enabling clients to relay media when direct UDP fails.
  • Videobridge: The jvb container doesn’t directly manage TURN but relies on clients using the TURN server for media relay.
  • Stable-10008 Updates: No significant TURN-related changes from stable-9955 to stable-10008 (per changelog), ensuring this config remains current.

Testing TURN Functionality

  1. Check Logs:

    • Videobridge logs:

      docker logs jitsi_jvb_1

      Look for successful ICE candidate establishment via TURN.

    • Web logs:

      docker logs jitsi_web_1

      Confirm TURN server details are loaded.

  2. Join a Meeting:

    • Use https://your-jitsi.localtunnel.me in a browser and your React Native app.
    • Test audio/video across different devices/networks (e.g., mobile data vs. Wi-Fi).
  3. Browser Dev Tools:

    • In Chrome, go to chrome://webrtc-internals during a call. Look for turn: candidates in the ICE connection stats, indicating TURN usage.

Troubleshooting TURN Issues

Common Problems

  • TURN Server Unreachable:

    • Symptoms: No audio/video; logs show ICE failures.
    • Fix: Verify TURN_HOST and TURNS_HOST are correct and reachable (ping openrelay.metered.ca from WSL).
    • Test: curl -v telnet://openrelay.metered.ca:80 (should connect).
  • Invalid Credentials:

    • Symptoms: Logs mention authentication errors.
    • Fix: Double-check TURN_CREDENTIALS format (username:password).
  • Media Still Fails:

    • Cause: Public TURN server overloaded or incompatible.
    • Fix: Switch to another server (e.g., turn:turn.matrix.org:3478 with matrix:matrix) or self-host.

Debugging Commands

  • Check JVB Config:

    docker exec jitsi_jvb_1 cat /config/sip-communicator.properties

    Ensure no conflicting settings (e.g., DISABLE_TCP_HARVESTER should not be present in stable-10008).

  • Inspect Config.js:

    docker exec jitsi_web_1 cat /config/config.js

    Look for turncredentials or stunServers entries reflecting your TURN settings.


Self-Hosting a TURN Server (Optional)

For production or if public servers fail:

Install Coturn on a VPS

  1. Setup VPS: Use a provider like DigitalOcean ($5/month) with Ubuntu 22.04.

  2. Install Coturn:

    sudo apt update && sudo apt install -y coturn
  3. Configure Coturn (/etc/turnserver.conf):

    listening-port=3478
    tls-listening-port=443
    external-ip=YOUR_VPS_PUBLIC_IP
    realm=your-turn-domain.com
    user=testuser:testpass
    cert=/etc/letsencrypt/live/your-turn-domain.com/fullchain.pem
    pkey=/etc/letsencrypt/live/your-turn-domain.com/privkey.pem
    • Get SSL certs with Certbot: sudo certbot certonly --standalone -d your-turn-domain.com.
  4. Start Coturn:

    sudo systemctl enable coturn && sudo systemctl start coturn

Update Jitsi .env

TURN_HOST=your-turn-domain.com
TURN_PORT=3478
TURNS_HOST=your-turn-domain.com
TURNS_PORT=443
TURN_CREDENTIALS=testuser:testpass

Best Practices for Stable-10008

  • Use TLS: Prefer TURNS_HOST and port 443 for security and broader compatibility.

  • Monitor Usage: Public TURN servers may throttle; check logs for relay usage.

  • Fallback STUN: Add STUN servers in config.js (manual override) if needed:

    stunServers: [
        { urls: 'stun:stun.l.google.com:19302' }
    ]

This annex ensures your TURN setup is fully compatible with Jitsi Meet stable-10008, addressing your CGNAT and Localtunnel constraints. Let me know if you need help testing or tweaking it further!

Traefik Setup Cheatsheet for Jitsi with DuckDNS and Let's Encrypt

This cheatsheet will guide you through setting up Traefik as a reverse proxy for your self-hosted Jitsi instance, using a DuckDNS domain for dynamic DNS and Let's Encrypt for SSL certificates via ACME. It addresses the ACME authorization error you're encountering and ensures Traefik exposes the necessary ports correctly.


Prerequisites

  • A DuckDNS account with a subdomain (e.g., your-domain.duckdns.org).
  • Your DuckDNS token.
  • Docker and Docker Compose installed on your server.
  • Basic familiarity with Docker Compose files.

Step-by-Step Guide

1. Verify DuckDNS Setup

  • Check IP Update: Confirm that your DuckDNS domain (your-domain.duckdns.org) is updating with your public IP address. Since you mentioned this is working, you're good here!
  • Validate Token: Ensure you have the correct DuckDNS token from your DuckDNS account dashboard.

2. Configure Traefik Static Configuration

Traefik needs a static configuration to enable the ACME DNS challenge for Let's Encrypt with DuckDNS as the provider.

  • Option 1: Use traefik.yml
    Create a traefik.yml file with:

    entryPoints:
      web:
        address: ":80"
      websecure:
        address: ":443"
    
    certificatesResolvers:
      myresolver:
        acme:
          email: [email protected]
          storage: /acme.json
          dnsChallenge:
            provider: duckdns

    Mount it in your Docker Compose file (see step 4).

  • Option 2: Use Command-Line Arguments
    Add these to the command section in your Traefik service (see the minimal example below).

3. Set Environment Variables

Pass your DuckDNS token to Traefik via an environment variable:

environment:
  - DUCKDNS_TOKEN=your-token-here

4. Mount Volumes Correctly

Traefik stores ACME certificates in a file (typically acme.json), which must be persistent and have proper permissions.

  • Docker Compose Configuration:

    volumes:
      - ./acme.json:/acme.json
  • Prepare the File: Before running Docker Compose, create the file with correct permissions:

    touch acme.json && chmod 600 acme.json

5. Expose Ports

Since you mentioned Traefik isn't exposing ports, ensure ports 80 and 443 are mapped correctly:

ports:
  - "80:80"
  - "443:443"
  • Note: These ports must be open on your server and forwarded through your router if behind a NAT.

6. Set Up Router for Jitsi

Configure Traefik to route traffic to your Jitsi service. Add these labels to the Jitsi web service in your Docker Compose file:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.jitsi.rule=Host(`your-domain.duckdns.org`)"
  - "traefik.http.routers.jitsi.entrypoints=websecure"
  - "traefik.http.routers.jitsi.tls=true"
  - "traefik.http.routers.jitsi.tls.certresolver=myresolver"
  • Replace your-domain.duckdns.org with your actual DuckDNS domain.
  • If Jitsi uses multiple services (e.g., web, prosody), apply this to the web service or adjust as needed.

7. Check Traefik Logs

The ACME authorization error details are in Traefik’s logs.

  • View Logs:

    docker-compose logs traefik
  • Enable Debug Logging (optional): Add to command in Docker Compose:

    command:
      - "--log.level=DEBUG"
  • Look for messages like "cannot set TXT record" or "timeout" to pinpoint the issue.

8. Test with a Simple Service

To isolate the ACME issue, test Traefik with a simple service like whoami:

whoami:
  image: traefik/whoami
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.whoami.rule=Host(`whoami.your-domain.duckdns.org`)"
    - "traefik.http.routers.whoami.entrypoints=websecure"
    - "traefik.http.routers.whoami.tls=true"
    - "traefik.http.routers.whoami.tls.certresolver=myresolver"
  • If this works (you can access https://whoami.your-domain.duckdns.org), the issue is Jitsi-specific. If not, focus on Traefik/ACME setup.

9. Use Let's Encrypt Staging Server (Optional)

To avoid rate limits while debugging:

  • Add to ACME configuration in traefik.yml:

    caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
  • Remove this line once everything works to switch to production.

10. Ensure Network Access

  • Verify Traefik can reach DuckDNS and Let's Encrypt. Test internet access from the container:

    docker exec -it <traefik-container-name> ping duckdns.org
  • Check your firewall/router doesn’t block outbound connections.

11. Verify Domain Configuration

  • Ensure the domain in your router rule (your-domain.duckdns.org) matches your DuckDNS subdomain exactly—no typos!

12. Handle Propagation Delays (If Needed)

If the DNS challenge times out due to slow TXT record propagation:

  • Add to dnsChallenge in traefik.yml:

    dnsChallenge:
      provider: duckdns
      delayBeforeCheck: 60  # Wait 60 seconds

Common Pitfalls

  • Typos: Double-check DUCKDNS_TOKEN, domain names, and configuration keys.
  • Permissions: acme.json must be writable (chmod 600).
  • Ports: Forgetting to map 80/443 in Docker Compose or open them in your network.
  • Challenge Type: Ensure you’re using dnsChallenge, not httpChallenge, since you’re behind a dynamic IP.

Minimal Example

Here’s a minimal Docker Compose file to test Traefik with DuckDNS and whoami. Once this works, adapt it for Jitsi:

services:
  traefik:
    image: traefik:v2.10
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./acme.json:/acme.json
    command:
      - "--api.insecure=true"  # For debugging; remove in production
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=duckdns"
      - "--certificatesresolvers.myresolver.acme.email=your-email@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/acme.json"
    environment:
      - DUCKDNS_TOKEN=your-token-here

  whoami:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.your-domain.duckdns.org`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls=true"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"
  • Steps:
    1. Create acme.json: touch acme.json && chmod 600 acme.json.
    2. Replace your-token-here, [email protected], and your-domain.duckdns.org.
    3. Run: docker-compose up -d.
    4. Check https://whoami.your-domain.duckdns.org.

Next Steps

  • If the minimal example works, integrate these settings into your Jitsi Docker Compose file.
  • For Jitsi’s multi-container setup (e.g., web, prosody), apply the router labels to the web service or adjust based on your configuration.
  • If issues persist, share Traefik logs for further debugging.

Good luck with your Jitsi setup—hope this gets you up and running! Let me know if you need more help!

Walkthrough: Self-Hosting Jitsi with Localtunnel Behind CGNAT

This guide will help you set up a Jitsi instance, expose it publicly using Localtunnel to bypass CGNAT, and integrate it with a React Native app. It includes a free TURN server to handle media traffic (audio/video), ensuring everything works smoothly despite your network constraints.


Prerequisites

Before starting, ensure you have the following:

  • Windows 11 with WSL2: WSL2 installed and set as the default (wsl --set-default-version 2 in PowerShell).
  • Docker & Docker Compose: Installed in your WSL Linux distro (e.g., Ubuntu).
  • Node.js & npm: Required for Localtunnel.
  • Internet Access: Via a mobile hotspot or similar (behind CGNAT).

Step 1: Set Up Your WSL Environment

  1. Open WSL Terminal:

    • Launch your WSL distro (e.g., wsl or wsl -d ubuntu in PowerShell).
  2. Install Docker & Docker Compose:

    sudo apt update && sudo apt install -y docker.io docker-compose
    sudo systemctl start docker
    sudo usermod -aG docker $USER  # Log out and back in after this
  3. Install Node.js & npm:

    sudo apt install -y nodejs npm
  4. Install Localtunnel:

    sudo npm install -g localtunnel

Step 2: Clone and Configure Jitsi

  1. Clone Jitsi Docker Repository:

    git clone https://github.com/jitsi/docker-jitsi-meet.git
    cd docker-jitsi-meet
  2. Copy Environment File:

    cp env.example .env
  3. Edit .env for Localtunnel and TURN:

    • Open .env with an editor (e.g., nano .env) and configure:

      CONFIG=/home/your-username/.jitsi-meet-cfg  # Replace with your WSL username
      HTTP_PORT=8000
      TZ=UTC
      DISABLE_HTTPS=1  # Localtunnel provides SSL
      PUBLIC_URL=https://your-jitsi.localtunnel.me  # Update after Step 4
      DOCKER_HOST_ADDRESS=172.18.x.x  # Your WSL IP (see below)
      
      # TURN server config (using a free public TURN server)
      ENABLE_TURN=1
      TURN_RELAY_HOST=turn.matrix.org
      TURN_RELAY_PORT=3478
      TURN_RELAY_USER=matrix
      TURN_RELAY_PASS=matrix
    • Find Your WSL IP: Run ip addr show eth0 | grep inet in WSL and note the IP (e.g., 172.18.x.x).

  4. Generate Passwords:

    ./gen-passwords.sh
  5. Bind Jitsi Web to Localhost:

    • Edit docker-compose.yml (e.g., nano docker-compose.yml):

      web:
        image: jitsi/web:stable-9646  # Latest stable version as of now
        ports:
          - "127.0.0.1:8000:80"
        # ... other settings remain unchanged

Step 3: Start Jitsi and Verify Locally

  1. Launch Jitsi:

    docker-compose up -d
  2. Test Locally in WSL:

    curl http://localhost:8000
    • You should see HTML output indicating Jitsi is running.
  3. Forward to Windows (Optional):

    • In an elevated PowerShell (run as Administrator):

      netsh interface portproxy add v4tov4 listenport=8000 listenaddress=0.0.0.0 connectport=8000 connectaddress=172.18.x.x
      • Replace 172.18.x.x with your WSL IP.
    • Open http://localhost:8000 in a Windows browser to confirm.


Step 4: Expose Jitsi with Localtunnel

  1. Start Localtunnel:

    lt --port 8000 --subdomain your-jitsi
    • Replace your-jitsi with a unique name (e.g., jitsi-myapp).
    • Without --subdomain, you’ll get a random URL (e.g., https://random-string.localtunnel.me).
    • Note the output URL (e.g., https://your-jitsi.localtunnel.me).
  2. Update PUBLIC_URL in .env:

    • Edit .env and set:

      PUBLIC_URL=https://your-jitsi.localtunnel.me
  3. Restart Jitsi:

    docker-compose down && docker-compose up -d

Step 5: Integrate with React Native

  1. Install Jitsi SDK:

    • In your React Native project directory:

      npm install @jitsi/react-native-sdk
  2. Configure Jitsi in Your App:

    • Add this code to your app:

      import JitsiMeet, { JitsiMeetView } from '@jitsi/react-native-sdk';
      
      const App = () => {
        const conferenceOptions = {
          url: 'https://your-jitsi.localtunnel.me',
          room: 'MyTestRoom',
        };
      
        return (
          <JitsiMeetView
            options={conferenceOptions}
            style={{ flex: 1 }}
          />
        );
      };
      
      export default App;
  3. Ensure HTTPS Compatibility:

    • For Android, verify AndroidManifest.xml enforces HTTPS:

      <application
        ...
        android:usesCleartextTraffic="false">

Step 6: Test End-to-End

  1. Open Jitsi in Browser:

    • Visit https://your-jitsi.localtunnel.me and start a meeting.
  2. Join from React Native App:

    • Run your app (npx react-native run-android or run-ios) and join the same room.
  3. Verify Audio/Video:

    • Ensure audio and video work. The TURN server should relay media traffic successfully.

Troubleshooting Tips

  • Localtunnel URL Changes:

    • If you restart Localtunnel without --subdomain, the URL will change. Use --subdomain for consistency.
  • TURN Server Issues:

    • Check Jitsi logs if media fails:

      docker logs jitsi_jvb_1
  • Firewall:

    • Ensure your mobile hotspot allows outbound TCP connections on port 443 (Localtunnel uses HTTPS).
  • Performance:

    • For better quality, consider a dedicated TURN server (e.g., Coturn on a VPS).

Why This Solution Works

  • Localtunnel: Tunnels your local Jitsi instance to a public HTTPS URL, bypassing CGNAT.
  • TURN Server: Relays media traffic over TCP, ensuring audio/video works behind restrictive networks.
  • No Reverse Proxy: Keeps setup simple by directly exposing Jitsi via Localtunnel.
  • Free & Flexible: Requires no domain or VPS, making it ideal for testing or small-scale use.

Optional Enhancements

  • Persistent Localtunnel URL:

    • Use --subdomain consistently or opt for Ngrok’s paid tier for a static URL.
  • Security:

    • Enable authentication in .env:

      ENABLE_AUTH=1
      ENABLE_GUESTS=1
  • Production Use:

    • For reliability, deploy Jitsi on a VPS with a public IP or use a managed TURN server.

This walkthrough provides a fully functional Jitsi instance behind CGNAT, integrated with your React Native app, using current tools and best practices. If you run into issues, check the logs or let me know for further assistance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment