Deploying Jitsi Meet with Docker and Nginx Proxy Manager

Learn how to self-host Jitsi Meet with Docker Compose and Nginx Proxy Manager. This detailed guide covers directory setup, HTTPS management, UDP port configuration, and admin account creation for a fully secure video conferencing platform.

Deploying Jitsi Meet with Docker and Nginx Proxy Manager

Introduction: Why Self-Host Jitsi Meet?

Jitsi Meet is an open-source WebRTC-powered video conferencing platform that rivals Zoom or Google Meet — but with one crucial difference: you control it completely.

Unlike proprietary services that store your video data in the cloud, Jitsi Meet allows you to host your own meetings, on your own hardware, under your own rules. For developers, educators, and privacy-focused teams, that’s a game changer.

In this tutorial, we’ll walk through deploying Jitsi Meet with Docker Compose, fronted by Nginx Proxy Manager (NPM) for HTTPS and reverse proxy management. You’ll also learn to organize your project folders, expose required ports, and create admin users for secure access control.


Step 1: Folder Structure — The Foundation of a Clean Setup

Before writing your docker-compose.yaml, it’s best practice to create a clear folder hierarchy. Each service — Prosody, Jicofo, JVB, and the Web interface — will store its configuration in mounted volumes for persistence.

Here’s the recommended layout:

jitsi-meet-docker/
├── docker-compose.yaml        # Main Docker Compose stack
│
├── prosody/                   # Prosody (XMPP signaling server)
├── jicofo/                    # Jicofo (conference orchestrator)
├── jvb/                       # Jitsi Video Bridge (media router)
├── web/                       # Web frontend (Nginx + Jitsi Meet UI)
└── transcripts/               # Meeting transcripts and captions

Step 2: Create the Folder Structure in One Command

You can create all necessary directories with a single command from the root of your project:

mkdir -p prosody jicofo jvb web transcripts

Once this command runs, your file structure will be ready to mount into the containers defined in docker-compose.yaml.


Step 3: Complete docker-compose.yaml Configuration

Now, let’s build the main Docker Compose file that brings all the components together.
This configuration assumes you are using Nginx Proxy Manager (NPM) for SSL and reverse proxy management.
If you’re not using NPM, a commented section shows how to enable internal HTTPS via Let’s Encrypt inside the jitsi-web container.


🧩 docker-compose.yaml

services:
  # 1) XMPP Server — Handles signaling, sessions, and authentication
  jitsi-prosody:
    image: jitsi/prosody:stable
    container_name: jitsi-prosody
    restart: unless-stopped
    networks: [jitsi-meet, edge]
    environment:
      - TZ=Asia/Seoul                    # Change for production
      - XMPP_DOMAIN=meet.jitsi
      - XMPP_AUTH_DOMAIN=auth.meet.jitsi
      - XMPP_GUEST_DOMAIN=guest.meet.jitsi
      - XMPP_MUC_DOMAIN=muc.meet.jitsi
      - XMPP_INTERNAL_MUC_DOMAIN=internal-muc.meet.jitsi
      - ENABLE_AUTH=1                # Enable authentication
      - AUTH_TYPE=internal
      - ENABLE_GUESTS=1              # Allow guests to join meetings
      - GLOBAL_MODULES=smacks
      - GLOBAL_CONFIG=restrict_room_creation = true;
      # 🔑 These credentials must match across prosody, jicofo, and jvb
      - JICOFO_AUTH_USER=focus
      - JICOFO_AUTH_PASSWORD=focuspass!  # Change for production
      - JICOFO_COMPONENT_SECRET=focuscomponent! # Change for production
      - JVB_AUTH_USER=jvb
      - JVB_AUTH_PASSWORD=jvbpass!       # Change for production
    volumes:
      - ./prosody:/config

  # 2) Conference Orchestrator — Jicofo manages meetings and participants
  jitsi-jicofo:
    image: jitsi/jicofo:stable
    container_name: jitsi-jicofo
    restart: unless-stopped
    depends_on: [jitsi-prosody]
    networks: [jitsi-meet, edge]
    environment:
      - TZ=Asia/Seoul                   # Change for production
      - XMPP_DOMAIN=meet.jitsi
      - XMPP_AUTH_DOMAIN=auth.meet.jitsi
      - XMPP_SERVER=jitsi-prosody
      - JICOFO_AUTH_USER=focus
      - JICOFO_AUTH_PASSWORD=focuspass!         # Must match prosody
      - JICOFO_COMPONENT_SECRET=focuscomponent! # Must match prosody
      - ENABLE_AUTH=1
    volumes:
      - ./jicofo:/config

  # 3) Jitsi Video Bridge — Handles audio/video media streams
  jitsi-jvb:
    image: jitsi/jvb:stable
    container_name: jitsi-jvb
    restart: unless-stopped
    depends_on: [jitsi-prosody]
    networks: [jitsi-meet, edge]
    ports:
      - "10000:10000/udp"   # ✅ WebRTC media port (must be open externally)
      # - "4443:4443/tcp"   # Optional TCP fallback port
    environment:
      - TZ=Asia/Seoul                     # Change for production
      - XMPP_SERVER=jitsi-prosody
      - XMPP_DOMAIN=meet.jitsi
      - XMPP_AUTH_DOMAIN=auth.meet.jitsi
      - JVB_AUTH_USER=jvb
      - JVB_AUTH_PASSWORD=jvbpass!        # Change for production
      # Public IP or domain for NAT environments
      - JVB_ADVERTISE_ADDRESS=jitsi.example.com
    volumes:
      - ./jvb:/config

  # 4) Web Frontend — Serves Jitsi Meet UI via internal Nginx
  jitsi-web:
    image: jitsi/web:stable
    container_name: jitsi-web
    restart: unless-stopped
    depends_on: [jitsi-prosody, jitsi-jicofo, jitsi-jvb]
    networks: [jitsi-meet, edge]
    # ⚠ No exposed ports here — Nginx Proxy Manager will handle public access
    environment:
      - TZ=Asia/Seoul                    # Change for production
      - PUBLIC_URL=https://jitsi.example.com
      - XMPP_DOMAIN=meet.jitsi
      - XMPP_AUTH_DOMAIN=auth.meet.jitsi
      - XMPP_BOSH_URL_BASE=http://jitsi-prosody:5280
      - ENABLE_GUESTS=1
      - ENABLE_AUTH=1
      # ▼▼▼▼▼ Use this only if NOT using NPM (direct HTTPS inside container) ▼▼▼▼▼
      # - ENABLE_LETSENCRYPT=1
      # - LETSENCRYPT_DOMAIN=jitsi.example.com
      # - LETSENCRYPT_EMAIL=admin@example.com
      # - HTTPS_PORT=443
      # ports:
      #   - "80:80"
      #   - "443:443"
      # ▲▲▲▲▲ Comment out the above if using NPM for certificate management ▲▲▲▲▲
    volumes:
      - ./web:/config
      - ./transcripts:/usr/share/jitsi-meet/transcripts

networks:
  jitsi-meet:
  edge:
    external: true

Step 4: Understanding the Configuration

Network Architecture

  • Two Docker networks are used:
    • jitsi-meet (internal communication)
    • edge (shared with Nginx Proxy Manager)

This allows NPM to reach the jitsi-web container through its internal hostname (jitsi-web:80).

Port Exposure

  • The only public port required is UDP 10000 (media traffic).
  • Optionally, TCP 4443 can serve as a fallback.
  • No ports from jitsi-web are exposed because NPM handles external traffic.

Authentication

  • Internal authentication is enabled (ENABLE_AUTH=1, AUTH_TYPE=internal).
  • Only authenticated users can create rooms.
  • Guests can join existing rooms if ENABLE_GUESTS=1.

Persistent Volumes

  • All configuration and user data persist across restarts.

You can safely update images with:

docker compose pull && docker compose up -d

Step 5: Nginx Proxy Manager (NPM) Configuration

NPM acts as your HTTPS reverse proxy and SSL manager. It simplifies domain routing and certificate renewals with Let’s Encrypt.

Proxy Host Setup

  1. Add a New Proxy Host
    • Domain Names: jitsi.example.com
    • Scheme: http
    • Forward Hostname / IP: jitsi-web
    • Forward Port: 80
  2. Enable Options
    • ✅ Block Common Exploits
    • ✅ WebSocket Support
  3. SSL Tab
    • Request a new SSL certificate via Let’s Encrypt
    • Enable Force SSL, HTTP/2, and HSTS

To ensure stable WebSocket connections and prevent timeouts, add the following in the Advanced tab:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
client_max_body_size 0;
proxy_set_header X-Forwarded-Proto $scheme;

Step 6: Start the Stack

Once your docker-compose.yaml and NPM configuration are ready:

docker compose up -d

To verify the services:

docker ps

You should see containers named:

jitsi-prosody
jitsi-jicofo
jitsi-jvb
jitsi-web

Access the site via your domain (e.g., https://jitsi.example.com).
If everything is configured correctly, you’ll see the Jitsi Meet landing page.


Step 7: The UDP 10000 Requirement

This port is non-negotiable.
Jitsi’s media bridge (jvb) uses UDP 10000 for WebRTC communication. If it’s closed or filtered, participants won’t see or hear each other.

Open the port at all levels:

# Example (UFW firewall)
sudo ufw allow 10000/udp
sudo ufw reload

In cloud environments (AWS, GCP, etc.), also open it in your Security Group or Firewall Rules.


Step 8: Create an Admin Account

To create rooms and manage access, register an admin user inside the Prosody container:

docker exec -it jitsi-prosody /usr/bin/prosodyctl \
  --config /config/prosody.cfg.lua register admin meet.jitsi 'StrongPassword123!'

This creates an account admin@meet.jitsi.
To make this account a global administrator, edit:

./prosody/prosody.cfg.lua

Add:

admins = { "admin@meet.jitsi" }

Restart related services:

docker compose restart jitsi-prosody jitsi-jicofo jitsi-web

Now, when creating a new room, Jitsi will prompt for authentication.
Guests can join afterward if ENABLE_GUESTS=1.


Step 9: Verifying the Installation

Visit your domain:

https://jitsi.example.com

You should see the Jitsi interface load.

If the meeting connects but video/audio fails:

  • Verify UDP 10000 is open.
  • Confirm NPM has WebSocket Support enabled.
  • Use your browser’s developer console to check for failed BOSH or WebSocket requests.

Check logs:

docker logs jitsi-jvb
docker logs jitsi-prosody

Step 10: Performance & Quality Optimization

To make your deployment production-ready, tweak these parameters:

  • Enable a TURN Server:
    For restrictive networks, integrate coturn for relay support.
  • Limit Resolution:
    Reduce video quality to 720p or 360p to improve bandwidth efficiency.
  • Bandwidth Control:
    Use Jitsi’s config.js to limit max upload/download bitrate.
  • Enable TCP Fallback:
    Open port 4443/tcp as a backup route when UDP is blocked.

Step 11: Security Best Practices

Security should not be an afterthought.

  • Rotate secrets regularly:
    Change passwords for JICOFO_AUTH_PASSWORD, JVB_AUTH_PASSWORD, and JICOFO_COMPONENT_SECRET.
  • Restrict port exposure:
    Only UDP 10000 should be public.
  • Backup volumes:
    Archive the prosody, jicofo, jvb, and web directories periodically.

Monitor logs:

docker logs -f jitsi-prosody
docker logs -f jitsi-jvb

Apply automatic updates:

docker compose pull && docker compose up -d

Step 12: Troubleshooting Common Issues

Issue Cause Solution
Meeting connects but no video/audio UDP 10000 blocked Open port or configure TURN
Only one user visible NAT/Firewall interference Set JVB_ADVERTISE_ADDRESS correctly
Screen share drops Bandwidth limits or WebSocket timeout Increase read timeout in NPM
Can’t create rooms No admin account Register admin via prosodyctl
SSL errors NPM misconfiguration Reissue Let’s Encrypt certificate
Guests can’t join ENABLE_GUESTS disabled Set ENABLE_GUESTS=1 and restart

Conclusion: Your Open-Source Zoom Alternative

By following this guide, you now have a fully functional, self-hosted Jitsi Meet server powered by Docker and Nginx Proxy Manager.

You own the infrastructure, data, and user control — with modern WebRTC performance and enterprise-grade security.

From here, you can extend your deployment with:

  • JWT or SSO authentication (Keycloak/OIDC)
  • TURN/STUN servers for better connectivity
  • Monitoring via Prometheus or Grafana
  • Integration with your organization’s internal systems

In short:
You’ve built your own privacy-respecting, scalable alternative to Zoom — right on your own terms.

“Owning your communication infrastructure isn’t just a technical choice.
It’s an act of digital independence.”

External Reference:
🔗 Official Jitsi Docker Repository