VPS Tutorials
Deploy Jitsi Meet on a VPS (2026): Docker, HTTPS, 10000 UDP, Authentication, TURN and Recording
Set up a video conferencing system on an Ubuntu VPS using the official docker-jitsi-meet stable release, covering configuration choices, domain HTTPS, 10000 UDP, room authentication, TURN, recording planning, backup and upgrades, and audio/video troubleshooting.

Jitsi Meet is an open-source video conferencing system suitable for team meetings, online teaching, remote interviews and customer communication. It does not require participants to install an account system; they can join a meeting by opening a link in the browser, and authentication can be used to restrict room creation to internal members only.
This article deploys Jitsi Meet on an Ubuntu VPS using the official docker-jitsi-meet stable release, covering server configuration, domain HTTPS, the 10000/UDP media port, room authentication, TURN, backup, upgrades and common troubleshooting. As of September 1, 2026, the latest official stable release is stable-11146-2.
| Scenario | Suitable | Notes |
|---|---|---|
| Small internal team meetings | Suitable | Can restrict room creation to authenticated users only |
| Online courses, public sharing | Suitable | Supports waiting room, raise hand, chat and screen sharing |
| Remote customer communication | Suitable | Join with a domain, no forced client installation |
| Large-scale live streaming | Should not use a single Jitsi instance alone | Requires Jibri, a CDN or a dedicated streaming platform |
| Strict compliance recording and long-term archiving | Requires additional design | Recording, storage, permissions and retention policies must be planned separately |
Jitsi's video streams are mainly forwarded by Jitsi Videobridge. It is not an ordinary web application: CPU and memory matter, but public bandwidth, packet loss, jitter, UDP reachability and the regions where participants are located often have a more direct impact on the experience.
The following configurations are conservative starting points, not fixed concurrency guarantees. Enabling HD, screen sharing, recording or many cameras will significantly increase resource requirements.
| Usage | Recommended configuration | Notes |
|---|---|---|
| Testing and 2-4 person meetings | 2 cores, 2GB, 30GB SSD | Suitable for feature validation, not recommended for sustained full load |
| Small team daily meetings | 4 cores, 8GB, 50GB+ NVMe | Leaves headroom for JVB, Jicofo, Prosody and Docker |
| Multiple simultaneous meetings | 8 cores, 16GB and up | Requires stress testing uplink bandwidth, packet loss and regional latency |
| Recording or live streaming | Plan Jibri separately | Browser rendering, encoding and file writing are resource-intensive |
Prioritize:
- VPS with 1Gbps or higher port speed and sufficient monthly traffic;
- Data centers close to the main participants;
- Public IPv4, ability to open UDP, clear security group rules;
- Stable CPU performance without severe overselling;
- Support for snapshots and console rescue.
If you are still choosing between 2 cores/2GB and 4 cores/8GB, you can refer to How to choose VPS specifications. For video conferencing, prioritize network quality and memory headroom rather than just disk size.
Prepare the following:
- A fresh Ubuntu 24.04 LTS VPS;
- root or sudo SSH access;
- A domain, for example
meet.example.com; - An email address that can receive certificate notifications;
- The main regions of meeting users and a basic concurrency estimate.
Add a DNS A record:
meet.example.com A 203.0.113.10
Confirm the domain and public IP:
dig +short meet.example.com A
curl -4 ifconfig.me
The two results should match. If the VPS does not yet have IPv6 configured, do not add an AAAA record pointing to a wrong address.
The official Jitsi Docker documentation requires opening:
| Port | Protocol | Purpose |
|---|---|---|
| 22 | TCP | SSH management |
| 80 | TCP | HTTP redirect and certificate validation |
| 443 | TCP | HTTPS, WebSocket and web access |
| 10000 | UDP | Jitsi Videobridge audio/video media |
When using UFW, allow SSH first, then enable the firewall:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10000/udp
sudo ufw enable
sudo ufw status numbered
You also need to configure the same rules in the cloud provider's security group. Changing only UFW or only the security group is not enough. Before doing this, you can read How to recover from SSH lockout caused by UFW to avoid locking yourself out of the current SSH session.
10000/UDP is the port most easily overlooked. If the web page opens but multi-person meetings have no sound, freeze or disconnect after a few seconds, check it first.
Update the system and install Docker, Compose and extraction tools:
sudo apt update
sudo apt upgrade -y
sudo apt install -y docker.io docker-compose-v2 unzip curl ca-certificates
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
If the system repository does not have docker-compose-v2, install the Compose Plugin from the official Docker repository instead of falling back to the long-unmaintained Python docker-compose v1.
Do not clone master directly for production. The official documentation states that master with daily unstable images is for development, and production deployments should download a Release.
This article pins stable-11146-2:
cd /opt
sudo curl -fL https://github.com/jitsi/docker-jitsi-meet/archive/refs/tags/stable-11146-2.zip -o jitsi.zip
sudo unzip jitsi.zip
sudo mv docker-jitsi-meet-stable-11146-2 jitsi-meet
sudo chown -R "$USER":"$USER" /opt/jitsi-meet
cd /opt/jitsi-meet
cp env.example .env
Starting from this version, containers run as non-root with a read-only filesystem. The structure of configuration, persistent data and runtime directories differs from older versions, so upgrading an old instance cannot be done by simply replacing the image tag.
Generate internal XMPP passwords for Jicofo, JVB, Jigasi and Jibri:
cd /opt/jitsi-meet
./gen-passwords.sh
The script modifies .env and preserves .env.bak. These passwords are used for communication between components; they must not be left empty, and do not manually reuse the same password.
Create the directories required by stable-11146 and later versions:
sudo mkdir -p /opt/jitsi-meet-cfg/{web,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri,transcriber}
sudo mkdir -p /opt/jitsi-meet-cfg/storage/{jibri,prosody,transcripts,web}
sudo mkdir -p /opt/jitsi-meet-cfg/tmp/{web-crontabs,web-load-test}
sudo chmod 777 /opt/jitsi-meet-cfg/storage/{jibri,prosody,transcripts,web}
sudo chmod 777 /opt/jitsi-meet-cfg/tmp/{web-crontabs,web-load-test}
The official current containers run as an unprivileged user with UID/GID 1000, and the storage and tmp directories that need to be written must be writable. If directories are missing or permissions are wrong, the container will refuse to start.
Edit .env:
nano /opt/jitsi-meet/.env
At minimum modify or add:
CONFIG=/opt/jitsi-meet-cfg
HTTP_PORT=80
HTTPS_PORT=443
TZ=Asia/Shanghai
PUBLIC_URL=https://meet.example.com
JVB_ADVERTISE_IPS=203.0.113.10
ENABLE_LETSENCRYPT=1
LETSENCRYPT_DOMAIN=meet.example.com
[email protected]
LETSENCRYPT_ACME_SERVER=letsencrypt
ENABLE_HTTP_REDIRECT=1
ENABLE_PREJOIN_PAGE=1
ENABLE_LOBBY=1
ENABLE_REQUIRE_DISPLAY_NAME=1
RESTART_POLICY=unless-stopped
JVB_ADVERTISE_IPS must be filled with the public IP that clients can actually reach. If the VPS is behind NAT, has multiple network interfaces, or has migrated IPs, an incorrect value here will cause signaling to work but media to fail.
Do not delete the password lines generated by gen-passwords.sh. After saving, check the key variables first:
cd /opt/jitsi-meet
grep -E '^(CONFIG|PUBLIC_URL|JVB_ADVERTISE_IPS|HTTP_PORT|HTTPS_PORT|ENABLE_LETSENCRYPT|LETSENCRYPT_DOMAIN)=' .env
Do not output the full .env to tickets, screenshots or public repositories, because it contains internal passwords.
Pull images and start:
cd /opt/jitsi-meet
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
Watch the first startup logs:
sudo docker compose logs -f --tail=100 web prosody jicofo jvb
After confirming there is no continuous restarting, press Ctrl+C to exit the logs, then test:
curl -I http://meet.example.com
curl -I https://meet.example.com
sudo ss -lntup | grep -E ':(80|443|10000)\b'
Open https://meet.example.com in a browser and create a random room. Test with at least two devices on different networks, for example a computer on broadband and a phone with Wi-Fi disabled using mobile data.
Jitsi's connection is roughly divided into two parts:
- The browser accesses the web page, BOSH or WebSocket signaling over 443/TCP;
- Audio and video preferentially communicate with Jitsi Videobridge over 10000/UDP.
Therefore, the homepage and room page opening only proves that HTTPS is basically working, not that the media path is working. When troubleshooting, run:
sudo ufw status verbose
sudo ss -lunp | grep ':10000'
sudo docker compose logs --tail=200 jvb
Also confirm the cloud security group, host firewall, carrier UDP policies and JVB_ADVERTISE_IPS. Cloudflare's normal proxy does not forward Jitsi's 10000/UDP media traffic.
The simplest and most stable approach is to keep meet.example.com as DNS Only, so that both HTTPS and UDP go directly to the VPS. It is also recommended to use DNS Only when issuing certificates for the first time, to reduce ACME validation variables.
Cloudflare's orange cloud can proxy web pages and WebSocket, but it does not replace JVB's UDP media forwarding, nor can it hide the media server's origin IP. Incorrectly assuming that "with the orange cloud enabled, you don't need to open 10000/UDP" will result in being able to enter a room but not being able to talk normally.
If you really want to proxy the web layer, verify separately:
- Whether
/xmpp-websocketWebSocket works; - Whether the JVB Candidate the browser receives is the correct public IP;
- Whether 10000/UDP is directly reachable;
- Whether the HTTPS mode is Full (strict) rather than Flexible.
By default, an anonymous deployment allows any visitor to create a room, which may be abused once exposed to the internet. Small teams can enable the internal authentication already present in the Docker configuration:
ENABLE_AUTH=1
ENABLE_GUESTS=1
AUTH_TYPE=internal
Apply the configuration:
cd /opt/jitsi-meet
sudo docker compose up -d
Create a moderator account:
sudo docker compose exec prosody \
prosodyctl --config /config/prosody.cfg.lua \
register host meet.jitsi 'replace-with-a-strong-password'
Once enabled, authenticated users are responsible for creating rooms, and guests can join after the moderator allows them. Test the moderator and guest flows separately with an incognito browser and a normal browser.
Internal authentication suits a small number of fixed moderators. When you need enterprise SSO, fine-grained permissions, or meeting identities issued by a business system, use a JWT/OIDC solution. The official project has marked the old manual Secure Domain process as deprecated; do not copy outdated Prosody configuration tutorials for new systems.
Some corporate networks, hotel Wi-Fi and strict firewalls block UDP or peer-to-peer connections. In this case, you can deploy Coturn so that one-to-one connections are relayed through TURN when direct connection is not possible.
The official Jitsi TURN documentation uses:
3478/UDP: regular TURN;5349/TCP: TLS TURN;- For particularly strict networks, TLS 443 can be planned, but it requires SNI multiplexing with web HTTPS or a separate server.
Do not write static TURN usernames and passwords directly into the frontend configuration. Browsers can see these credentials, and others may steal your relay traffic. A more robust approach is to use short-term credentials issued through Prosody's external service module.
TURN is not a replacement for 10000/UDP, nor can it turn a low-bandwidth VPS into a high-concurrency meeting platform. It mainly provides a fallback for connections on restricted networks and will increase the server's outbound traffic.
Server-side recording and live streaming use Jibri. Jibri launches a browser, renders the meeting, captures audio and video and encodes it, consuming significantly more resources than the basic Jitsi components.
Do not enable Jibri directly on a 2-core, 2GB meeting host. For production, prepare a separate host for recording or at least reserve dedicated CPU, memory and disk, and plan:
- The recording file storage directory and capacity;
- Whether users are clearly informed that recording is in progress;
- File access permissions and retention periods;
- Local backlog when uploading to object storage fails;
- Concurrency limits for recording multiple meetings simultaneously.
If you only need temporary personal saving, evaluate browser-local recording first rather than immediately deploying a full Jibri.
Basic Jitsi does not save the content of every meeting like a database application, but the following files determine whether you can rebuild quickly:
/opt/jitsi-meet/.env
/opt/jitsi-meet/docker-compose.yml
/opt/jitsi-meet-cfg/
Record the current version and container status before backing up:
cd /opt/jitsi-meet
grep -m1 -o 'stable-[0-9-]*' docker-compose.yml
sudo docker compose ps
cd /
sudo tar \
--exclude='opt/jitsi-meet-cfg/tmp' \
-czf /root/jitsi-backup-$(date +%F).tar.gz \
opt/jitsi-meet/.env \
opt/jitsi-meet/docker-compose.yml \
opt/jitsi-meet-cfg
Copy backups to object storage or another server; do not leave them only on the same system disk. .env contains passwords, so backup files need to be encrypted and access-restricted. You can combine this with the VPS backup and restore drill tutorial to validate the recovery process.
The official recommendation is to re-download the latest Release rather than switching the production directory to master. Before updating, create a VPS snapshot and configuration backup, then read the Release Notes.
Starting from stable-11146, containers run as non-root with a read-only filesystem, and the storage and tmp directories were added. When upgrading from older versions, you must first create and authorize these directories according to the official documentation.
Regular update process:
- Download and extract the new Release to a new directory;
- Compare the old and new
env.exampleand Compose files; - Copy the existing
.envand add variables required by the new version; - Preserve the
/opt/jitsi-meet-cfgpersistent directories; - Pull the new images and run
docker compose up -d; - Re-test audio and video with two external networks.
Do not assume a cross-version upgrade is complete just by running docker compose pull, because the Compose structure, directories and environment variables may also change.
cd /opt/jitsi-meet
sudo docker compose ps
sudo docker compose logs --tail=200 web prosody jicofo jvb
On stable-11146 and later, first check whether the storage and tmp directories exist and are writable by UID 1000, then check whether internal passwords are empty.
dig +short meet.example.com A
sudo ss -lntp | grep -E ':(80|443)\b'
sudo docker compose logs --tail=200 web
Check DNS, wrong AAAA records, 80/443 security groups, Cloudflare proxy and certificate request frequency. Repeatedly deleting /opt/jitsi-meet-cfg/storage/web will lose existing certificates and may trigger ACME rate limits.
One-to-one may use P2P, and adding a third person switches to JVB. Focus on checking 10000/UDP, JVB logs, JVB_ADVERTISE_IPS and the cloud security group.
Check Prosody, Jicofo and WebSocket:
sudo docker compose logs --since=10m prosody jicofo web
curl -I https://meet.example.com/xmpp-websocket
The reverse proxy or CDN must support WebSocket Upgrade. Time desynchronization can also affect authentication and certificates; use timedatectl status to check NTP.
Self-signed certificates and HTTP pages do not meet the browser's secure context requirements. You must use valid HTTPS and confirm that the domain the phone accesses matches the certificate. The official Jitsi documentation also explicitly states that mobile apps cannot use the default self-signed certificate.
First distinguish server resource issues from network issues:
docker stats --no-stream
free -h
df -h
sudo ss -s
sudo docker compose logs --since=10m jvb
Observe VPS outbound bandwidth, packet loss, CPU Steal, client regions and UDP reachability. Simply adding memory cannot fix cross-continent high latency or peak-hour line congestion.
- Use the official stable Release, not
masterandunstable -
PUBLIC_URLmatches the real HTTPS domain -
JVB_ADVERTISE_IPSis the correct public IP - 80/TCP, 443/TCP and 10000/UDP are open in both the security group and UFW
- The Let's Encrypt certificate is valid and HTTP automatically redirects to HTTPS
- Two devices on different networks have completed audio/video and screen sharing tests
- Strangers cannot freely create or abuse meeting rooms
-
.envand the configuration directory are encrypted and backed up off-site - A snapshot exists before upgrading, and the current stable version is recorded
- TURN has been tested for restricted-network compatibility, not just port openness
The criterion for a successful Jitsi Meet deployment is not "the homepage opens", but that external networks can complete multi-person meetings through the correct HTTPS, WebSocket and UDP media paths. Get PUBLIC_URL, JVB_ADVERTISE_IPS, 10000/UDP and room creation permissions right first, then consider recording, live streaming, SSO and multi-Videobridge scaling.