Open-source alternatives guide
How to Self-Host Jitsi Meet 2026
Self-host Jitsi Meet in 2026. Apache 2.0, ~22K stars — Zoom alternative with no account required. Docker Compose setup, TURN server, JWT auth for private.
TL;DR
Jitsi Meet (Apache 2.0, ~22K GitHub stars, Java/JavaScript) is a self-hosted video conferencing platform — the most popular open source Zoom alternative. No account required for guests, unlimited meeting duration, E2E encryption, screen sharing, recording, and breakout rooms. Zoom charges $15.99/month per host. Jitsi self-hosted is free and handles groups of 35 participants well on modern hardware (500 participants with dedicated infrastructure).
Key Takeaways
- Jitsi Meet: Apache 2.0, ~22K stars — no-account video calls, unlimited duration
- No guest accounts: Share a URL, anyone joins — no signup needed
- STUN/TURN: Required for participants behind NAT/firewalls (coturn)
- JWT auth: Optional — require a JWT token to create/join rooms (private deployment)
- Recording: Jibri service records meetings to MP4 and uploads to S3
- Embeddable: Add Jitsi to any web app via the Jitsi Meet External API (JavaScript)
Jitsi vs Zoom vs BigBlueButton
| Feature | Jitsi Meet | Zoom | BigBlueButton |
|---|---|---|---|
| License | Apache 2.0 | Proprietary | LGPL 3.0 |
| GitHub Stars | ~22K | — | ~9K |
| Cost (self-hosted) | Free | $15.99/host/mo | Free |
| Guest accounts | Not required | Required | Not required |
| Max participants | ~35-50 (practical) | 1000+ | 200+ |
| E2E encryption | Yes | Limited | No |
| Recording | Yes (Jibri) | Yes | Yes |
| Breakout rooms | Yes | Yes | Yes |
| Whiteboard | No | Yes | Yes |
| Best for | Small teams | Enterprise | Education |
Part 1: Docker Setup
# docker-compose.yml
services:
# NGINX + Let's Encrypt frontend:
web:
image: jitsi/web:stable
container_name: jitsi_web
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ${CONFIG}/web:/config:Z
- ${CONFIG}/web/crontabs:/var/spool/cron/crontabs:Z
- ${CONFIG}/transcripts:/usr/share/jitsi-meet/transcripts:Z
environment:
- ENABLE_AUTH
- ENABLE_GUESTS
- ENABLE_LOBBY
- ENABLE_XMPP_WEBSOCKET
- JICOFO_AUTH_USER
- LETSENCRYPT_DOMAIN=${XMPP_DOMAIN}
- LETSENCRYPT_EMAIL
- PUBLIC_URL
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_BOSH_URL_BASE
- XMPP_GUEST_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_RECORDER_DOMAIN
- ETHERPAD_URL_BASE
# XMPP server:
prosody:
image: jitsi/prosody:stable
container_name: jitsi_prosody
restart: unless-stopped
expose:
- "5222"
- "5347"
- "5280"
volumes:
- ${CONFIG}/prosody/config:/config:Z
- ${CONFIG}/prosody/prosody-plugins-custom:/prosody-plugins-custom:Z
environment:
- AUTH_TYPE
- ENABLE_AUTH
- ENABLE_GUESTS
- GLOBAL_MODULES
- GLOBAL_CONFIG
- LDAP_URL
- LDAP_BASE
- LDAP_BINDDN
- LDAP_BINDPW
- LDAP_FILTER
- LDAP_AUTH_METHOD
- LDAP_VERSION
- LDAP_USE_TLS
- LDAP_TLS_CIPHERS
- LDAP_TLS_CHECK_PEER
- LDAP_TLS_CACERT_FILE
- LDAP_TLS_CACERT_DIR
- LDAP_START_TLS
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_GUEST_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_MODULES
- XMPP_MUC_MODULES
- XMPP_INTERNAL_MUC_MODULES
- XMPP_RECORDER_DOMAIN
- JICOFO_COMPONENT_SECRET
- JICOFO_AUTH_USER
- JICOFO_AUTH_PASSWORD
- JVB_AUTH_USER
- JVB_AUTH_PASSWORD
- JIGASI_XMPP_USER
- JIGASI_XMPP_PASSWORD
- JIBRI_XMPP_USER
- JIBRI_XMPP_PASSWORD
- JIBRI_RECORDER_USER
- JIBRI_RECORDER_PASSWORD
- JWT_APP_ID
- JWT_APP_SECRET
- JWT_ACCEPTED_ISSUERS
- JWT_ACCEPTED_AUDIENCES
- JWT_ASAP_KEYSERVER
- JWT_ALLOW_EMPTY
- JWT_AUTH_TYPE
- JWT_TOKEN_AUTH_MODULE
- TZ
# Conference focus component:
jicofo:
image: jitsi/jicofo:stable
container_name: jitsi_jicofo
restart: unless-stopped
volumes:
- ${CONFIG}/jicofo:/config:Z
environment:
- AUTH_TYPE
- BRIDGE_AVG_PARTICIPANT_STRESS
- BRIDGE_STRESS_THRESHOLD
- ENABLE_AUTH
- JICOFO_COMPONENT_SECRET
- JICOFO_AUTH_USER
- JICOFO_AUTH_PASSWORD
- JICOFO_ENABLE_BRIDGE_HEALTH_CHECKS
- JICOFO_CONF_INITIAL_PARTICIPANT_WAIT_TIMEOUT
- JICOFO_CONF_SINGLE_PARTICIPANT_TIMEOUT
- JICOFO_ENABLE_HEALTH_CHECKS
- JICOFO_SHORT_ID
- JIBRI_BREWERY_MUC
- JIBRI_PENDING_TIMEOUT
- JIBRI_XMPP_USER
- JIBRI_XMPP_PASSWORD
- JIGASI_BREWERY_MUC
- JIGASI_XMPP_USER
- JIGASI_XMPP_PASSWORD
- MAX_BRIDGE_PARTICIPANTS
- OCTO_BRIDGE_SELECTION_STRATEGY
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_SERVER
- TZ
# Video bridge:
jvb:
image: jitsi/jvb:stable
container_name: jitsi_jvb
restart: unless-stopped
ports:
- "10000:10000/udp" # Media traffic
- "4443:4443"
volumes:
- ${CONFIG}/jvb:/config:Z
environment:
- DOCKER_HOST_ADDRESS
- ENABLE_COLIBRI_WEBSOCKET
- ENABLE_JVB_XMPP_SERVER
- JVB_AUTH_USER
- JVB_AUTH_PASSWORD
- JVB_BREWERY_MUC
- JVB_PORT
- JVB_MUC_NICKNAME
- JVB_STUN_SERVERS
- JVB_TCP_HARVESTER_DISABLED
- PUBLIC_URL
- XMPP_AUTH_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_SERVER
- JVB_ADVERTISE_IPS
- TZ
Simplified .env for quick setup
# .env
CONFIG=~/.jitsi-meet-cfg
TZ=America/Los_Angeles
PUBLIC_URL=https://meet.yourdomain.com
DOCKER_HOST_ADDRESS=YOUR_SERVER_PUBLIC_IP
# Authentication:
ENABLE_AUTH=0
ENABLE_GUESTS=1
# XMPP secrets (generate random):
JICOFO_COMPONENT_SECRET=$(openssl rand -hex 16)
JICOFO_AUTH_USER=focus
JICOFO_AUTH_PASSWORD=$(openssl rand -hex 16)
JVB_AUTH_USER=jvb
JVB_AUTH_PASSWORD=$(openssl rand -hex 16)
# Let's Encrypt:
LETSENCRYPT_EMAIL=admin@yourdomain.com
XMPP_DOMAIN=meet.yourdomain.com
# Download official docker-compose:
git clone https://github.com/jitsi/docker-jitsi-meet.git
cd docker-jitsi-meet
cp env.example .env
# Edit .env with your values
mkdir -p ~/.jitsi-meet-cfg/{web/crontabs,web/letsencrypt,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
docker compose up -d
Part 2: TURN Server (Required for NAT)
Without TURN, participants behind NAT (corporate networks, firewalls) can't connect. Set up coturn:
# Add to docker-compose.yml:
services:
coturn:
image: coturn/coturn:alpine
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf:ro
# coturn.conf
listening-port=3478
tls-listening-port=5349
relay-ip=YOUR_SERVER_IP
external-ip=YOUR_PUBLIC_IP
realm=meet.yourdomain.com
server-name=meet.yourdomain.com
fingerprint
lt-cred-mech
user=jitsi:your-turn-password
log-file=/var/log/turnserver.log
# Update JVB to use TURN:
JVB_STUN_SERVERS=meet.yourdomain.com:3478
Part 3: JWT Authentication (Private Deployment)
Require a JWT token to create/join rooms — prevents public access:
# .env additions:
ENABLE_AUTH=1
ENABLE_GUESTS=0
AUTH_TYPE=jwt
JWT_APP_ID=myapp
JWT_APP_SECRET=$(openssl rand -hex 32)
# Generate a JWT for a user:
import jwt
import time
JWT_APP_ID = "myapp"
JWT_APP_SECRET = "your-secret"
payload = {
"context": {
"user": {
"name": "Alice Smith",
"email": "alice@company.com",
"avatar": "https://example.com/alice.jpg"
}
},
"aud": JWT_APP_ID,
"iss": JWT_APP_ID,
"sub": "meet.yourdomain.com",
"room": "team-standup", # Specific room or "*" for all
"exp": int(time.time()) + 3600, # 1 hour expiry
"nbf": int(time.time()) - 10,
}
token = jwt.encode(payload, JWT_APP_SECRET, algorithm="HS256")
meeting_url = f"https://meet.yourdomain.com/team-standup?jwt={token}"
Part 4: Recording with Jibri
Jibri records meetings to MP4 using Chrome headless:
# Add to docker-compose.yml:
services:
jibri:
image: jitsi/jibri:stable
container_name: jibri
restart: unless-stopped
volumes:
- ${CONFIG}/jibri:/config:Z
- /dev/shm:/dev/shm # Shared memory for Chrome
- ./recordings:/srv/recordings # Where recordings are saved
shm_size: '2gb'
cap_add:
- SYS_ADMIN
devices:
- /dev/snd:/dev/snd
environment:
- ENABLE_RECORDING
- XMPP_SERVER
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_MUC_DOMAIN
- JIBRI_XMPP_USER
- JIBRI_XMPP_PASSWORD
- JIBRI_RECORDER_USER
- JIBRI_RECORDER_PASSWORD
- JIBRI_FINALIZE_RECORDING_SCRIPT_PATH
- JIBRI_STRIP_DOMAIN_JID
- ENABLE_STATS_D
- TZ
Part 5: Embedding in Your App
Add Jitsi to any web application:
<!-- Embed Jitsi in your page: -->
<div id="jaas-container"></div>
<script src="https://meet.yourdomain.com/external_api.js"></script>
<script>
const api = new JitsiMeetExternalAPI("meet.yourdomain.com", {
roomName: "team-standup",
width: "100%",
height: 600,
parentNode: document.querySelector('#jaas-container'),
configOverwrite: {
startWithAudioMuted: true,
startWithVideoMuted: false,
disableDeepLinking: true,
},
interfaceConfigOverwrite: {
TOOLBAR_BUTTONS: ['microphone', 'camera', 'closedcaptions', 'desktop', 'chat', 'raisehand'],
},
userInfo: {
displayName: "Alice",
email: "alice@company.com"
}
});
// Events:
api.addEventListener('videoConferenceJoined', () => console.log('Joined!'));
api.addEventListener('participantJoined', (participant) => console.log('Joined:', participant));
api.addEventListener('readyToClose', () => api.dispose());
</script>
Maintenance
# Update Jitsi:
docker compose pull
docker compose up -d
# Check which components are running:
docker compose ps
# View logs per component:
docker compose logs -f jvb
docker compose logs -f prosody
docker compose logs -f jicofo
# Test your setup:
# Visit https://meet.yourdomain.com and create a test meeting
# Use webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ to test TURN
Why Self-Host Jitsi Meet
Jitsi Meet's value proposition for self-hosters comes down to three things: cost, control, and customization.
On the cost side, Zoom charges $15.99 per licensed host per month. For a team of 10 people who all need to host meetings, that's $159.90/month — nearly $1,920/year. A VPS capable of running Jitsi comfortably (4 vCPUs, 8GB RAM) runs $20-40/month on most providers. Over a year, self-hosting saves you $1,500 or more, and that savings scales linearly as your team grows. Jitsi's open source model means you keep every feature — recording, breakout rooms, E2E encryption — without paying per-seat license fees.
Data ownership matters especially for video conferencing because your meetings may contain proprietary strategy discussions, customer negotiations, or HR conversations. Zoom's data resides on Zoom's servers. With self-hosted Jitsi, your meeting data — including any recordings — never leaves your infrastructure. You control retention, deletion, and access logs. For regulated industries (healthcare, legal, finance), this isn't optional; it's required.
Customization opens doors that SaaS can't. You can white-label the interface completely, replacing Jitsi branding with your own. You can restrict room creation to authenticated users via JWT while still allowing guests to join via shared links. You can embed the video component directly inside your own application using the External API. Enterprises can integrate with LDAP or SAML for SSO.
When NOT to self-host Jitsi: If you routinely host meetings with 100+ participants, Jitsi's architecture requires serious infrastructure — a dedicated JVB server with at least 8 vCPUs and good uptime. For occasional large meetings, Zoom or Google Meet is genuinely more practical. Also, Jitsi's mobile experience is solid but not identical to Zoom's — test it with your team before committing. And if your team is non-technical and you have no one to maintain the server, the management overhead (updates, SSL cert renewals, monitoring) can outweigh the cost savings.
Prerequisites
Before deploying Jitsi, make sure your environment is ready. This is not a simple single-container deployment — Jitsi runs four coordinated services (web frontend, Prosody XMPP server, Jicofo, and JVB video bridge), each with its own resource requirements.
Server specs: Minimum 4 vCPUs and 8GB RAM for up to 35 participants. For larger meetings or concurrent sessions, scale up accordingly. A 2 vCPU/2GB server will run Jitsi but will struggle the moment a meeting has more than 5-6 participants with video on. See our VPS comparison for self-hosters for the best-value providers at the 4 vCPU tier — Hetzner's CX31 (~$8/month) is a popular choice.
Operating system: Ubuntu 22.04 LTS is the recommended choice. The Docker images are tested heavily against this base, and Ubuntu's 5-year security support window means you won't be forced to migrate the OS anytime soon.
Domain and DNS: Jitsi needs a dedicated subdomain (e.g., meet.yourdomain.com) with an A record pointing to your server's public IP. Let's Encrypt SSL requires this to be resolvable before startup.
Network ports: UDP 10000 (JVB media traffic) must be open and reachable from the internet. Without this, participants behind NAT or firewalls will use TURN, which adds latency. TCP 80 and 443 are needed for the web frontend and Let's Encrypt.
Skill level: Intermediate. You need to be comfortable editing YAML files, running Docker Compose commands, and basic Linux administration. The trickiest part is NAT traversal — if your server is behind its own NAT, you need to configure DOCKER_HOST_ADDRESS correctly.
Production Security Hardening
Running a public Jitsi server without hardening is an invitation to abuse — rooms can be hijacked, bandwidth consumed, and your server used for unauthorized meetings. Follow the self-hosting security checklist for comprehensive coverage, but here are the critical steps specific to Jitsi:
Firewall (UFW): Jitsi needs more ports than a typical web app because of media traffic.
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10000/udp # JVB media traffic
sudo ufw allow 3478/udp # TURN server (if using coturn)
sudo ufw allow 5349/tcp # TURN over TLS
sudo ufw enable
Enable JWT authentication: An open Jitsi server is a free video conferencing service for anyone who discovers your URL. Enable JWT auth (see Part 3) and only distribute tokens to your team. Even if you want semi-public access, at minimum enable the Lobby feature so a moderator must approve each participant.
Secrets management: Never put your JICOFO_COMPONENT_SECRET, JVB_AUTH_PASSWORD, or JWT_APP_SECRET directly in docker-compose.yml. These end up in git history and visible in docker inspect output. Use a .env file:
# .env (add to .gitignore immediately)
JICOFO_COMPONENT_SECRET=your-random-secret
JVB_AUTH_PASSWORD=your-random-password
JWT_APP_SECRET=your-jwt-secret
echo ".env" >> .gitignore
Disable SSH password authentication: On Ubuntu, edit /etc/ssh/sshd_config: set PasswordAuthentication no and PermitRootLogin no. Only use SSH key authentication. Restart with sudo systemctl restart ssh.
Automatic security updates:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Regular backups: Jitsi's configuration lives in ~/.jitsi-meet-cfg. Back this up regularly so you can restore your entire setup after a server failure. See our guide on automated server backups with restic for a production-tested approach.
Performance and Capacity Planning
Understanding Jitsi's performance characteristics helps you right-size your infrastructure and avoid surprises during important meetings.
The JVB (Jitsi Video Bridge) is the performance-critical component. Unlike traditional MCU (Multipoint Control Unit) architectures that transcode every stream server-side, Jitsi uses an SFU (Selective Forwarding Unit) model — it routes video streams between participants without decoding and re-encoding. This is dramatically more efficient: a modern 4-core server can handle 35-50 simultaneous video participants with the SFU model, whereas an MCU would max out at 10-15.
Bandwidth is the other constraint. Each video participant generates approximately 500 Kbps to 2 Mbps depending on resolution and frame rate. For 20 participants all sending 1 Mbps video, your JVB needs 20 Mbps inbound from participants plus 19 × 20 = 380 Mbps outbound to forward each participant's stream to all others. This scales quadratically with participant count, which is why Jitsi recommends 35-50 as a practical upper limit for single-JVB deployments.
For larger meetings, Jitsi supports "cascaded" JVBs across multiple servers using the Octo protocol. Each additional JVB doubles your capacity. The jicofo component handles bridge selection — it picks the least-loaded bridge based on participant count.
Screen sharing deserves special attention. A screen share stream can easily reach 4-5 Mbps, significantly more than webcam video. If your users frequently share high-resolution screens, factor this into bandwidth planning. You can limit screen sharing quality in Jitsi's config to reduce the impact.
Monitoring your Jitsi instance gives you early warning before capacity issues hit. The JVB exposes a Prometheus-compatible metrics endpoint at :8080/metrics. Track jitsi_videobridge_conferences, jitsi_videobridge_participants, and jitsi_videobridge_endpoints_sending_audio to understand real-time load. If you're running Grafana Loki, integrate these metrics alongside your application logs for full observability.
For meetings that absolutely require high reliability, schedule your Jitsi updates during off-hours, set up a health check at https://meet.yourdomain.com with an uptime monitor like Uptime Kuma, and configure the JVB with enough buffer capacity that losing one meeting room doesn't cascade to others.
Troubleshooting Common Issues
Participants can't connect — black screen or "connecting" forever
This is the most common Jitsi problem and is almost always a NAT/TURN issue. The JVB (video bridge) needs to be reachable at your server's public IP on UDP port 10000. Check:
- Is
DOCKER_HOST_ADDRESSset to your server's public IP in.env? This is the #1 overlooked setting. - Is UDP 10000 open in your firewall?
sudo ufw statusand check your cloud provider's security group. - Test with
nc -u your-public-ip 10000from a remote machine. - If your server is behind NAT, you may need coturn — see Part 2.
Let's Encrypt certificate fails
Jitsi's built-in Let's Encrypt requires port 80 to be reachable. Verify nmap -p 80 your-domain.com from outside. Also check that LETSENCRYPT_DOMAIN matches your actual DNS record exactly. Rate limits apply — if you've failed 5 times in an hour, wait before retrying. Check logs with docker compose logs -f web.
Prosody fails to start or authentication errors
Usually a mismatch between the XMPP secrets in .env. Regenerate all secrets, delete the config directories under ~/.jitsi-meet-cfg/prosody/, and restart from scratch. docker compose logs -f prosody will show authentication failure details.
Recording with Jibri silently fails
Jibri uses Chrome headless and requires /dev/shm to be at least 2GB. Check docker stats jibri for memory usage. Also verify ENABLE_RECORDING=1 and that the Jibri XMPP credentials match between Jibri and Prosody. Jibri logs are verbose: docker compose logs -f jibri | grep -E "ERROR|WARN".
High CPU usage during meetings
The JVB is the most CPU-intensive component. Each active video stream requires ~5-10% CPU on a modern core. If you're seeing high CPU with only a few participants, check if you accidentally left JVB_TCP_HARVESTER_DISABLED=false — TCP fallback is inefficient. Also, screen sharing is more CPU-intensive than webcam video; consider limiting it in interfaceConfigOverwrite.
Can't join after enabling JWT auth
After enabling ENABLE_AUTH=1 and AUTH_TYPE=jwt, all room creation and joining requires a valid JWT. The most common mistake is clock skew — if your server time is more than a few seconds off, JWTs with nbf or exp will fail. Run sudo timedatectl status and ensure NTP is active.
Before going to production, test your Jitsi deployment under realistic load. Run a call with 10-15 simultaneous video participants and monitor CPU utilization — the Jitsi Videobridge (JVB) component handles video routing and is the most resource-intensive part. A 4 vCPU, 8GB RAM instance handles approximately 30-40 concurrent participants comfortably. Beyond that, deploy a second JVB node and configure the Octo cascading feature to distribute load across multiple videobridge instances. For most self-hosted use cases (team meetings, internal all-hands, smaller webinars), a single well-provisioned VPS handles the load without configuration.
See all open source video conferencing tools at OSSAlt.com/categories/communication.
See open source alternatives to Jitsi on OSSAlt.
The SaaS-to-Self-Hosted Migration Guide (Free PDF)
Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.
Join 300+ self-hosters. Unsubscribe in one click.