Video conferencing has become central to distributed teams, yet most organizations rely on SaaS platforms that store meeting metadata, recordings, and participant logs on infrastructure they do not control. Jitsi Meet is a fully open-source video conferencing stack that supports high-definition calls, screen sharing, breakout rooms, live streaming, and end-to-end encryption. Because the entire signalling and media path runs on your own servers, you retain custody of conversation metadata, can enforce authentication policies tied to your identity provider, and avoid per-seat licensing costs.
This guide deploys Jitsi Meet on Ubuntu with Docker Compose, Caddy for automatic HTTPS, and the official Jitsi web, prosody, jicofo, and jvb containers. By the end, you will have a production-ready conferencing server accessible at a public domain, with secure room creation, moderator controls, and the option to enable JWT authentication or LDAP integration for internal teams.
Architecture and flow overview
Caddy sits at the edge and terminates TLS with automatically managed Letβs Encrypt certificates. It reverse-proxies HTTPS traffic to the Jitsi web container on port 80, and also forwards colibri websocket traffic to the JVB container so that media bridges can communicate over secure channels. The web container serves the React frontend and loads configuration from environment variables at runtime. Prosody acts as the XMPP server, handling room creation, participant presence, and authentication hooks. Jicofo manages conference focus state, deciding which participant sends media to whom and orchestrating multiparty video routing. JVB (Jitsi Videobridge) receives encrypted RTP streams from clients, selects which streams to forward based on speaker activity and viewport size, and sends them back without decrypting content when selective forwarding is used.
All services run on an isolated Docker bridge network. The only ports exposed to the host are 80 and 443 for Caddy, plus UDP 10000 for JVB media traffic. Internal XMPP traffic between prosody, jicofo, and jvb remains inside the container network and is unreachable from the public internet. Persistent volumes keep prosody configuration, Jicofo state, JVB statistics, and Caddy certificates across container restarts and image upgrades. If you need high availability, you can run additional JVB containers on separate hosts and register them with the same prosody instance using the colibri REST API.
Prerequisites
- Ubuntu 22.04 or 24.04 LTS server with at least 2 vCPU, 4 GB RAM, and 40 GB SSD. For calls with more than twenty participants or heavy screen sharing, scale to 4 vCPU and 8 GB RAM, and provision a dedicated JVB host.
- Docker Engine 24.x and the Docker Compose plugin installed. Verify with
docker compose version. - A DNS A record pointing
meet.example.comto your server public IP, plus a second A record forjvb-meet.example.comif you plan to run JVB on a separate host later. - UDP port 10000 open in your cloud security group and UFW for JVB media traffic. TCP ports 80 and 443 are required for Caddy. Port 22 should be restricted to your management IP range.
- SMTP credentials if you plan to enable email invitations from the Jitsi web interface.
- UFW enabled with default-deny incoming, plus SSH allowed from your management IP range and the conferencing ports open as described above.
Step-by-step deployment
1. Create directory structure and environment file
Create a dedicated directory for the Jitsi stack, set strict permissions, and prepare an environment file for secrets that never enters version control. The environment file sets the public URL, XMPP domains, and strong passwords for the internal prosody accounts used by jicofo and jvb.
sudo mkdir -p /opt/jitsi-meet/{web-config,prosody-config,jicofo-config,jvb-config,caddy-data,caddy-config}
sudo useradd -r -s /usr/sbin/nologin -d /opt/jitsi-meet jitsi || true
sudo chown -R jitsi:jitsi /opt/jitsi-meet
sudo chmod 750 /opt/jitsi-meet
cd /opt/jitsi-meet
cat > .env <<'EOF'
CONFIG=/opt/jitsi-meet/web-config
PUBLIC_URL=https://meet.example.com
JICOFO_AUTH_PASSWORD=$(openssl rand -hex 24)
JVB_AUTH_PASSWORD=$(openssl rand -hex 24)
JIGASI_XMPP_PASSWORD=$(openssl rand -hex 24)
JIBRI_RECORDER_PASSWORD=$(openssl rand -hex 24)
JIBRI_XMPP_PASSWORD=$(openssl rand -hex 24)
ENABLE_AUTH=1
AUTH_TYPE=internal
ENABLE_GUESTS=1
EOF
chmod 600 .env
2. Write the Docker Compose file
Pin the stable Jitsi image tag, define restart policies, keep the XMPP and control networks private, and expose only the Caddy and JVB media ports on the host. The web container depends on prosody so that the XMPP server is ready before the web UI starts serving conference pages.
cat > docker-compose.yml <<'EOF'
services:
caddy:
image: caddy:2.8
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- jitsi
web:
image: jitsi/web:stable-9457
restart: unless-stopped
env_file: .env
volumes:
- ./web-config:/config
depends_on:
- prosody
networks:
- jitsi
prosody:
image: jitsi/prosody:stable-9457
restart: unless-stopped
env_file: .env
volumes:
- ./prosody-config:/config
- prosody-data:/prosody
networks:
- jitsi
jicofo:
image: jitsi/jicofo:stable-9457
restart: unless-stopped
env_file: .env
volumes:
- ./jicofo-config:/config
depends_on:
- prosody
networks:
- jitsi
jvb:
image: jitsi/jvb:stable-9457
restart: unless-stopped
env_file: .env
ports:
- "10000:10000/udp"
volumes:
- ./jvb-config:/config
depends_on:
- prosody
networks:
- jitsi
volumes:
prosody-data:
networks:
jitsi:
driver: bridge
EOF
3. Write the Caddyfile
Caddy handles HTTPS automatically, compresses responses, and forwards colibri websocket traffic to the JVB container on port 9090 so that clients can gather ICE candidates over secure channels. The standard reverse proxy forwards all other traffic to the web container.
cat > Caddyfile <<'EOF'
meet.example.com {
reverse_proxy /colibri-ws/* jvb:9090
reverse_proxy web:80
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
4. Configure Jitsi environment and generate secrets
The Jitsi images generate internal configuration files on first startup based on the environment variables. Source the .env file so that the generated passwords are available to the containers, then create the required configuration directories with the correct ownership.
set -a && source .env && set -a
sudo chown -R 1000:1000 web-config prosody-config jicofo-config jvb-config
5. Start the stack
Pull images, create volumes, and start services in detached mode. The prosody container will generate XMPP host certificates and user accounts on first launch. Wait about thirty seconds for the initialization to complete before opening the web interface.
docker compose pull
docker compose up -d
sleep 30
docker compose logs -f prosody
When you see log lines indicating that the MUC component and focus user are registered, open your browser and navigate to https://meet.example.com. Create a test room, join from a second device or browser profile, and verify that audio and video flow in both directions.
6. Create moderator credentials
With internal authentication enabled, only registered users can create rooms. Guests can join existing rooms, but they cannot start new conferences. Register the first moderator account inside the prosody container.
docker compose exec prosody prosodyctl --config /config/prosody.cfg.lua register admin meet.example.com $(openssl rand -hex 16)
Store the generated password in your team password manager. When you open the Jitsi web interface and click the authentication prompt, log in with [email protected] and the generated password to claim moderator rights for the room.
Configuration and secrets handling
Never commit the .env file to Git. Store a backup in your team password manager or a secrets vault such as HashiCorp Vault or 1Password. Rotate the JICOFO_AUTH_PASSWORD and JVB_AUTH_PASSWORD during annual security reviews. To rotate, stop the stack, update the passwords in .env, delete the prosody configuration volume so that prosody regenerates the accounts on next startup, and restart the services. Schedule a brief maintenance window because existing conferences will drop during the restart.
If you run Jitsi behind a corporate firewall, whitelist the UDP port range used by JVB and ensure that outbound STUN and TURN traffic is permitted. For restricted networks, deploy a separate coturn server and set TURN_CREDENTIALS and TURN_TRANSPORT in the environment file so that clients can fall back to relayed media when direct peer-to-peer or JVB connections are blocked.
Enable JWT authentication by setting AUTH_TYPE=jwt and providing JWT_APP_ID and JWT_APP_SECRET in the environment file. This replaces the internal prosody accounts with token-based room access, which is ideal for embedding Jitsi into existing applications. Generate tokens on your backend with a library such as pyjwt or jsonwebtoken, include the room name and user identity in the claims, and pass the token as a query parameter when constructing the Jitsi URL.
Verification
Confirm that all containers are healthy, that the public URL responds with a valid certificate, and that media traffic reaches the JVB container.
docker compose ps
curl -sS -o /dev/null -w "%{http_code}" https://meet.example.com
Test the colibri websocket endpoint and verify that JVB reports healthy statistics.
curl -sS -o /dev/null -w "%{http_code}" https://meet.example.com/colibri-ws/default-id
docker compose exec jvb curl -s http://localhost:8080/about/health
Create a three-party test call from different networks. Open the browser developer tools, inspect the WebRTC internals, and confirm that all participants show active inbound and outbound RTP streams with packet loss below one percent. If one participant is behind a symmetric NAT, verify that the ICE connection state reaches connected and that media flows through the JVB relay rather than failing with a timeout.
Common issues and fixes
Guests cannot create rooms and see an authentication prompt
This is expected behavior when ENABLE_AUTH=1 and ENABLE_GUESTS=1. Only registered users can create rooms. If you want open room creation, set ENABLE_AUTH=0 and restart the web and prosody containers. For a hybrid model, enable JWT authentication so that your application controls who can create rooms while still allowing anonymous guests to join.
Video or audio fails behind a corporate firewall
Strict firewalls often block UDP traffic or restrict outbound connections to well-known ports. If participants see black video tiles or hear no audio, check the browser WebRTC stats for ICE candidate failures. Deploy a TURN server on a separate host with public UDP and TCP ports, then configure TURN_TRANSPORT=tcp and TURN_CREDENTIALS in the Jitsi environment file so that clients can relay media through the TURN server when direct paths are unavailable.
Caddy serves a 502 error after restarting the stack
The web container may take longer to initialize than Caddy expects, especially during prosody certificate generation. Stop the stack, verify that the prosody configuration volume contains valid certificate files, and restart. If the issue persists, check that the PUBLIC_URL environment variable matches the Caddyfile domain exactly, including the HTTPS scheme.
docker compose down
docker compose up -d
sleep 45
curl -I https://meet.example.com
Recording does not start or Jibri containers crash
Jibri requires a dedicated XMPP user, a valid Chrome sandbox environment, and ALSA loopback audio devices on the host. If you are not running Jibri, ignore this error. To enable recording, add the Jibri service to the Compose file, map /dev/snd into the container, load the snd-aloop kernel module on the host, and set ENABLE_RECORDING=1 in the environment file. Recording consumes one CPU core per concurrent session, so provision a dedicated host for recording workloads.
High CPU usage during screen sharing with many participants
Screen sharing generates larger video frames than camera streams. The JVB forwarding strategy sends the full resolution to all viewers by default. Enable simulcast and layered forwarding so that viewers with smaller viewports receive lower resolution layers. Set ENABLE_SIMULCAST=1 in the environment file and verify in browser WebRTC internals that multiple spatial layers are being transmitted.
FAQ
Can I run JVB on a separate host for better scalability?
Yes. The JVB container is stateless except for its configuration and statistics. Copy the .env file to a second host, run only the JVB service in the Compose file, and point PROSODY_HOST to the primary prosody container IP or hostname. Register the new JVB instance with prosody using the colibri REST API, and Caddy will load-balance websocket connections across multiple bridges. Scale horizontally by adding more JVB hosts as call volume grows.
Does Jitsi Meet support end-to-end encryption?
Yes. Jitsi Meet supports browser-based end-to-end encryption using insertable streams. When enabled, media is encrypted inside the browser before transmission, and the JVB forwards encrypted packets without decryption. Note that this feature is experimental in some clients and may increase CPU usage on low-end devices. For most deployments, the standard transport encryption combined with controlled server custody provides adequate protection.
How do I restrict room creation to specific users?
Enable internal authentication or JWT authentication. With internal authentication, register moderator accounts using prosodyctl register inside the prosody container. With JWT authentication, issue tokens from your backend that include a room claim. Only users presenting a valid token can create rooms, while guests can join if they have the room URL.
What is the difference between Jicofo and JVB?
Jicofo is the conference focus. It decides which participant is the dominant speaker, assigns video channels, and coordinates room state. JVB is the videobridge. It receives encrypted RTP streams from participants and forwards them to other participants based on Jicofo instructions. Jicofo never handles media; JVB never handles signalling. Separating the roles allows you to scale media capacity independently of conference state management.
Can I use LDAP or Active Directory for authentication?
Yes. Prosody supports LDAP authentication through the mod_auth_ldap module. Mount a custom prosody configuration file that includes the LDAP module, set the bind DN, base DN, and filter in the environment variables, and restart prosody. Users will log in with their corporate credentials instead of local prosody accounts. This works with Active Directory, OpenLDAP, and FreeIPA.
How do I back up the Jitsi configuration?
Back up the environment file, the Caddy data directory, and the prosody configuration volume. The environment file contains the passwords that prosody uses to authenticate jicofo and jvb. The prosody volume contains the generated XMPP certificates and user accounts. Schedule a nightly tar archive to an S3-compatible object store or a separate backup server. Test restoration quarterly on a staging instance.
Is it safe to expose JVB UDP port 10000 directly to the internet?
Yes. JVB only accepts RTP and RTCP packets on that port. It does not run an HTTP server or expose a management interface on UDP 10000. For additional hardening, restrict the port to a known IP range in your cloud security group if all participants connect from a corporate VPN, or leave it open to the internet for public access. Monitor JVB logs for unusual traffic patterns.
Can I embed Jitsi Meet into my own web application?
Yes. Jitsi provides the Jitsi Meet External API library. Load the library from your application, create an iframe pointing to your self-hosted domain, and control the conference programmatically with JavaScript commands such as executeCommand and event listeners for participant joins and leaves. When using JWT authentication, generate a token on your backend and append it as a jwt query parameter to the iframe source.
Internal links
- Production Guide: Deploy Vaultwarden with Docker Compose + Caddy on Ubuntu
- Production Guide: Deploy Nextcloud with Docker Compose + NGINX + MariaDB + Redis on Ubuntu
- Production Guide: Deploy n8n with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.