How to Self-Host Jitsi Meet: Open Source Video Conferencing 2026
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
See all open source video conferencing tools at OSSAlt.com/categories/communication.