Skip to main content

How to Self-Host Jitsi Meet: Open Source Video Conferencing 2026

·OSSAlt Team
jitsivideo-conferencingzoomself-hostingdocker2026

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

FeatureJitsi MeetZoomBigBlueButton
LicenseApache 2.0ProprietaryLGPL 3.0
GitHub Stars~22K~9K
Cost (self-hosted)Free$15.99/host/moFree
Guest accountsNot requiredRequiredNot required
Max participants~35-50 (practical)1000+200+
E2E encryptionYesLimitedNo
RecordingYes (Jibri)YesYes
Breakout roomsYesYesYes
WhiteboardNoYesYes
Best forSmall teamsEnterpriseEducation

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.

Comments