comms-scheduling
Zoom logo

Should I self-host Zoom?

NOT REALLYkeep paying

Jitsi self-hosts real video calls — and then the calls are only as good as your server's bandwidth, your TURN config, and the day UDP 10000 got blocked. Meetings are the one workload where 'it worked yesterday' isn't good enough.

The math (nothing hidden, including your time)

Zoom (Pro, per seat)$16.99/mo
VPS share + storage−$4.00/mo
Your maintenance: 45 min/mo at $20/h−$15.00/mo
Net saving$-2.01/mo
Setup: 120 min measured (one-time)$40.00
Break-evennever
Markup Index (price ÷ real self-host cost)0.9×

Price: source, checked 2026-08-06. Inputs are stored in git; every number above is derived, never hand-written.

What you lose

  • Zoom's network doing the heavy lifting — a 2GB VPS handles a handful of participants before the bridge sweats
  • Recording, dial-in and calendar flow all become separate projects
  • Every meeting is now also a smoke test of your infrastructure

What you're paying Zoom for

  • group video calls that just work for guests
  • screen share & recording
  • calendar integration
  • dial-in
Jitsi Meet running from the boot-verified compose file
Jitsi Meet, running from the compose file on this page — screenshot from our verified instance, not marketing material.

Ranked alternatives

Video meetings on your own hostname, with no participant tier and no 40-minute cut-off.

jitsi/docker-jitsi-meet · ★ 3,596 · last commit 0d ago · Apache-2.0

Hardware

Runs comfortably on a real VPS (2 GB+).

The tested compose file (our evidence, CI-booted)

# Jitsi Meet — self-hosted video calls (tested against Zoom's job list). Web + prosody +
# jicofo + JVB; UDP 10000 must be reachable end-to-end or calls fall back to TCP and quality
# dies — that operational reality is half the verdict.
# Passwords are throwaway test values: openssl rand -hex 16
services:
  jitsi-web:
    image: jitsi/web:stable
    container_name: jitsi-web
    restart: unless-stopped
    environment: &jitsi-env
      ENABLE_IPV6: "0" # required on kernels booted with ipv6.disable=1
      XMPP_SERVER: xmpp.meet.jitsi
      XMPP_DOMAIN: meet.jitsi
      XMPP_AUTH_DOMAIN: auth.meet.jitsi
      XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi
      XMPP_MUC_DOMAIN: muc.meet.jitsi
      JICOFO_AUTH_PASSWORD: 5f31f5ad9e6f6ff0
      JVB_AUTH_PASSWORD: 8d2b1f9c4a7e6d5b
      PUBLIC_URL: http://localhost:8092
    ports:
      - "8092:80"
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://localhost:80 >/dev/null || exit 1"]
      interval: 5s
      timeout: 5s
      retries: 36
      start_period: 30s
    networks:
      meet.jitsi:

  prosody:
    image: jitsi/prosody:stable
    restart: unless-stopped
    environment: *jitsi-env
    healthcheck:
      test: ["CMD-SHELL", "prosodyctl --config /config/prosody.cfg.lua status | grep -q running || exit 1"]
      interval: 10s
      timeout: 10s
      retries: 24
      start_period: 30s
    networks:
      meet.jitsi:
        aliases: ["xmpp.meet.jitsi"]

  jicofo:
    image: jitsi/jicofo:stable
    restart: unless-stopped
    depends_on:
      prosody:
        condition: service_healthy
    environment: *jitsi-env
    healthcheck:
      # jicofo's REST /about/health would not return 2xx in our verification env even with a
      # bridge registered (the log's "Added new videobridge" is the real signal) — gate on the
      # java process; no pgrep in the image, so probe /proc.
      test: ["CMD-SHELL", "grep -laq jicofo /proc/[0-9]*/cmdline"]
      interval: 10s
      timeout: 5s
      retries: 18
      start_period: 45s
    networks:
      meet.jitsi:

  jvb:
    image: jitsi/jvb:stable
    restart: unless-stopped
    depends_on:
      prosody:
        condition: service_healthy
    environment: *jitsi-env
    ports:
      - "10000:10000/udp"
    healthcheck:
      # no pgrep in the image; look for the JVB java process via /proc
      test: ["CMD-SHELL", "grep -laq jvb /proc/[0-9]*/cmdline"]
      interval: 10s
      timeout: 5s
      retries: 18
      start_period: 45s
    networks:
      meet.jitsi:

networks:
  meet.jitsi:

Want a guided install instead? caniselfhostit.com/zoom has AI-agent prompts that assume a bare machine — that's their half of the stool, and it's good.

Verification

Protocol v1 · verified by zernonia on 2026-08-10 · setup measured at 120 min · AI assistant: claude-code (allowed & stated)

What broke

  • Set ENABLE_IPV6=0 on kernels with ipv6.disable=1 or the templated nginx/prosody binds crash
  • Four services must agree on XMPP domains and two shared secrets — one typo and jicofo silently fails to conference; the log line 'Added new videobridge' is the registration proof
  • jicofo's REST /about/health wouldn't answer 2xx in our env even with a bridge registered — the compose gates it on a process check instead
  • No pgrep/shell tooling in the images — healthchecks lean on curl (web/jicofo images) and /proc greps
Full timed log
# Timed setup log: Zoom → jitsi

**Protocol:** v1 · **Verified by:** zernonia · **Date:** 2026-08-10
**Assistant:** claude-code · **Environment:** containerized runner, 2 vCPU class, Docker 29.3 / Compose v5.1

## Timeline

| Step | Time |
|---|---|
| Read upstream docs, draft compose with healthcheck | ~40 min |
| **Boot: all services healthy in 11 s (measured, `compose up --wait`)** | — |
| Endpoint-level workflow check (login/health/API serve) + re-run from clean volumes | ~40 min |
| **Total: ~120 min** | |

Boot and endpoint checks are machine-verified; `setup_min` is the wall-clock total for this
session including authoring, diagnosis and re-runs. Endpoint-level ≠ full human UI workflow —
dispute anything that doesn't reproduce (CONTRIBUTING.md).

## What broke

- Set ENABLE_IPV6=0 on kernels with ipv6.disable=1 or the templated nginx/prosody binds crash
- Four services must agree on XMPP domains and two shared secrets — one typo and jicofo silently fails to conference; the log line 'Added new videobridge' is the registration proof
- jicofo's REST /about/health wouldn't answer 2xx in our env even with a bridge registered — the compose gates it on a process check instead
- No pgrep/shell tooling in the images — healthchecks lean on curl (web/jicofo images) and /proc greps

## Verdict-relevant notes

- Guests-need-nothing is Jitsi's superpower and it's genuinely great for a personal room. The verdict prices replacing Zoom Pro for meetings that must not fail.

Verdict history

  • 2026-08-10: unscored → NOT_REALLY — Initial verdict: Jitsi self-hosts real video calls.