A single-server Jitsi Meet installation works great, right up until it does not. The videobridge (JVB) is the component that carries all the audio and video streams, and it is almost always the first thing to run out of headroom. The fix is to split the deployment: keep the web frontend, prosody, and jicofo on one server, and run the videobridge on its own server, where you can scale it horizontally by simply adding more bridges.
This tutorial walks you through exactly that: a two-server Jitsi Meet installation on Ubuntu, with secure domain authentication enabled (only registered users can start meetings, guests can join) and websockets working end to end. At the end, you will know how to attach a third, fourth, or tenth videobridge with a few minutes of work each.
This guide is for sysadmins who have some Linux experience. If you have installed single-server Jitsi before, even better, but it is not required.
How a Multi-Server Jitsi Deployment Works
Jitsi Meet is not one program, it is a small family of services:
- jitsi-meet web: the React frontend served by nginx
- prosody: the XMPP server, the signaling backbone everything else connects to
- jicofo: the conference focus, it decides which videobridge hosts each conference
- jitsi-videobridge2 (JVB): the media router that actually forwards audio and video packets
In a multi-server setup, the first three stay together on the main server. Each JVB server runs only the videobridge, which connects back to prosody over XMPP and registers itself in a special chat room called the brewery ([email protected]). Jicofo watches that room, sees every connected bridge, and load balances conferences across them. That is the whole trick: adding capacity means adding another bridge to the room.
Each JVB also serves a colibri websocket that browsers use for fast bridge messaging. Browsers require TLS for websockets, so we terminate wss with a small nginx on the JVB server and proxy it as plain ws to the bridge, keeping the JVB itself free of TLS work.
The setup in this tutorial:
main.example.com (main server) : nginx, jitsi-meet, prosody, jicofo
jvb1.example.com (jvb server) : jitsi-videobridge2, nginx (wss termination)
Prerequisites
- Two servers running Ubuntu 22.04 or 24.04, each with its own public IP
- Two DNS records pointing at them, in this tutorial
main.example.comandjvb1.example.com - SSL certificates for both domains (Let’s Encrypt is fine, the installer can obtain the main server’s certificate for you)
- A user with
sudoprivileges on both servers - Open firewall ports:
443/tcpon both servers,10000/udpon the JVB server, and5222/tcpon the main server reachable from the JVB server
For sizing, the main server is light (2 CPU / 4 GB RAM handles a lot), while the JVB server benefits from CPU and especially network bandwidth.
Step 1: Install Jitsi Meet on the Main Server
First, install nginx before jitsi-meet. The jitsi-meet package checks which web server is present at install time, and we want it to configure nginx rather than fall back to jetty:
sudo apt update
sudo apt install nginx apt-transport-https curl gnupg2 -y
Add the prosody repository so you get a current prosody version:
sudo curl -sL https://prosody.im/files/prosody-debian-packages.key -o /etc/apt/keyrings/prosody-debian-packages.key
echo "deb [signed-by=/etc/apt/keyrings/prosody-debian-packages.key] http://packages.prosody.im/debian $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/prosody-debian-packages.list
Add the Jitsi repository:
curl -sL https://download.jitsi.org/jitsi-key.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/jitsi-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/" | sudo tee /etc/apt/sources.list.d/jitsi-stable.list
Now install everything:
sudo apt update
sudo apt install jitsi-meet
The installer asks two questions. For the hostname, enter your main domain, main.example.com. For the certificate, choose the Let’s Encrypt option and provide your email, or select your own certificate if you already have one.
Yes, we install the full jitsi-meet package including the local videobridge even though this server will not carry media. It is the easiest way to get all the prosody accounts and passwords generated correctly. We disable the local bridge next:
sudo systemctl stop jitsi-videobridge2
sudo systemctl disable jitsi-videobridge2
A note if you followed older tutorials: you no longer need to install Node.js (that is only for building the frontend from source) and you no longer need to downgrade prosody, current Jitsi packages work fine with prosody 0.12.
Step 2: Enable Secure Domain in Prosody
By default anyone who opens your site can start a conference. Secure domain changes that: starting a meeting requires a registered account, while guests can still join a meeting that a host has opened.
Open your site’s prosody config on the main server:
sudo nano /etc/prosody/conf.avail/main.example.com.cfg.lua
Find the main VirtualHost "main.example.com" block and change its authentication from jitsi-anonymous to internal_hashed:
VirtualHost "main.example.com"
authentication = "internal_hashed"
While you are in this block, confirm the modules_enabled list contains "websocket"; and "smacks";. Recent jitsi-meet packages ship them enabled already; add them if your file does not have them.
Then add a guest virtual host at the bottom of the file:
VirtualHost "guest.main.example.com"
authentication = "anonymous"
c2s_require_encryption = false
The guest domain is internal only, it does not need a DNS record or certificate. Save the file and restart prosody:
sudo systemctl restart prosody
Now register the account(s) that are allowed to host meetings:
sudo prosodyctl register bagus main.example.com StrongPasswordHere
Step 3: Configure Jicofo for Secure Domain
Tell jicofo to require authentication against the main domain:
sudo nano /etc/jitsi/jicofo/jicofo.conf
Add an authentication block inside the top-level jicofo block, so the file looks like this (your existing xmpp and bridge blocks stay as they are):
jicofo {
authentication: {
enabled: true
type: XMPP
login-url: main.example.com
}
xmpp: {
client: {
client-proxy: focus.main.example.com
}
}
bridge: {
brewery-jid: "[email protected]"
}
}
Note the brewery-jid: that is the chat room your remote videobridges will register in. Restart jicofo:
sudo systemctl restart jicofo
Step 4: Configure the Jitsi Meet Frontend
Open the frontend config:
sudo nano /etc/jitsi/meet/main.example.com-config.js
Set the guest domain so unauthenticated visitors are routed to the anonymous virtual host:
// When using authentication, domain for guest users.
anonymousdomain: 'guest.main.example.com',
Also confirm the websocket line is present and uncommented (it is the default on current versions):
websocket: 'wss://main.example.com/xmpp-websocket',
Save and exit. The main server is done.
Step 5: Grab the JVB Password from the Main Server
The videobridge authenticates to prosody with a generated password. Since the installer already created the jvb account on the main server, just read the password from the disabled local bridge’s config:
sudo grep PASSWORD /etc/jitsi/videobridge/sip-communicator.properties
org.jitsi.videobridge.xmpp.user.shard.PASSWORD=RvqxDnlH
Copy that value, the remote JVB will use it in the next step.
Step 6: Install the Videobridge on the JVB Server
On jvb1.example.com, add the same Jitsi repository:
sudo apt update
sudo apt install nginx apt-transport-https curl gnupg2 -y
curl -sL https://download.jitsi.org/jitsi-key.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/jitsi-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/" | sudo tee /etc/apt/sources.list.d/jitsi-stable.list
sudo apt update
sudo apt install jitsi-videobridge2
When the installer asks for the hostname, enter the main server’s domain (main.example.com), not the JVB’s own domain.
Now point the bridge at the main server’s prosody. Open the properties file:
sudo nano /etc/jitsi/videobridge/sip-communicator.properties
Make it look like this, using the password from Step 5 and a unique nickname for this bridge:
org.ice4j.ice.harvest.DISABLE_AWS_HARVESTER=true
org.ice4j.ice.harvest.STUN_MAPPING_HARVESTER_ADDRESSES=meet-jit-si-turnrelay.jitsi.net:443
org.jitsi.videobridge.ENABLE_STATISTICS=true
org.jitsi.videobridge.STATISTICS_TRANSPORT=muc
org.jitsi.videobridge.xmpp.user.shard.HOSTNAME=main.example.com
org.jitsi.videobridge.xmpp.user.shard.DOMAIN=auth.main.example.com
org.jitsi.videobridge.xmpp.user.shard.USERNAME=jvb
org.jitsi.videobridge.xmpp.user.shard.PASSWORD=RvqxDnlH
org.jitsi.videobridge.xmpp.user.shard.MUC_JIDS=JvbBrewery@internal.auth.main.example.com
org.jitsi.videobridge.xmpp.user.shard.MUC_NICKNAME=jvb1
org.jitsi.videobridge.xmpp.user.shard.DISABLE_CERTIFICATE_VERIFICATION=true
If the JVB server sits behind NAT, also add its addresses so ICE candidates are correct:
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=10.0.0.5
org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=203.0.113.10
Next, enable the colibri websocket in the bridge’s main config:
sudo nano /etc/jitsi/videobridge/jvb.conf
videobridge {
http-servers {
public {
port = 9090
}
}
websockets {
enabled = true
domain = "jvb1.example.com:443"
tls = true
server-id = "jvb1"
}
}
The server-id must match the MUC_NICKNAME you chose, and it will appear again in the nginx path below.
Step 7: Terminate WSS with Nginx on the JVB Server
Browsers connect to wss://jvb1.example.com/colibri-ws/jvb1/..., and nginx forwards it as plain websocket to the bridge on port 9090:
sudo nano /etc/nginx/sites-available/videobridge.conf
server {
listen 443 ssl;
server_name jvb1.example.com;
ssl_certificate /etc/letsencrypt/live/jvb1.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jvb1.example.com/privkey.pem;
location ~ ^/colibri-ws/jvb1/(.*) {
proxy_pass http://127.0.0.1:9090/colibri-ws/jvb1/$1$is_args$args;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
tcp_nodelay on;
}
}
You need a certificate for the JVB domain here. If you do not have one yet, Secure Nginx with Let’s Encrypt SSL Using Certbot on Ubuntu shows how to get it in a few minutes.
Enable the site and restart everything:
sudo ln -s /etc/nginx/sites-available/videobridge.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl restart jitsi-videobridge2
For a deeper explanation of why this websocket matters and how it behaves, see my dedicated post: Configure JVB WebSocket.
Step 8: Verify the Whole Setup
First, confirm the remote bridge registered with jicofo. On the main server:
sudo grep 'Added new videobridge' /var/log/jitsi/jicofo.log
You should see a line containing jvb1, meaning the bridge joined the brewery room and jicofo can use it. On older versions the log line says Added brewery instance instead; either way, your bridge nickname must appear.
Second, check the colibri websocket endpoint. Open this URL in a browser:
https://jvb1.example.com/colibri-ws/jvb1/
An HTTP 405 response is the success case here (the endpoint exists but does not accept plain GET). A 404 or a connection error means the nginx proxy or jvb.conf websocket block is wrong.
Third, check the prosody websocket the same way:
https://main.example.com/xmpp-websocket
This should return a short message saying it is a websocket endpoint, any 4xx other than 404 is fine.
Finally, the real test: open https://main.example.com, start a meeting, and log in with the user you registered with prosodyctl register. Join from a second device as a guest. Then confirm the media is flowing through the remote bridge and not the disabled local one:
sudo grep -c 'jvb1' /var/log/jitsi/jicofo.log
Or simply watch the JVB server’s bandwidth while the call runs.
Adding More Videobridges
This is where the architecture pays off. For each additional bridge, repeat Steps 6 and 7 on a new server with a new nickname (jvb2, jvb3, …) and its own domain and certificate. The XMPP password stays the same, the MUC_NICKNAME, server-id, and nginx path change. Each new bridge appears in the brewery room and jicofo starts scheduling conferences on it automatically. No changes are needed on the main server.
Common Mistakes and Troubleshooting
The bridge never appears in the jicofo log. Almost always an XMPP connection problem. Check the JVB log (/var/log/jitsi/jvb.log) for authentication failures, verify the password matches the main server’s, and confirm the JVB server can reach main.example.com:5222 (test with nc -zv main.example.com 5222).
Conferences connect but there is no audio or video. Port 10000/udp to the JVB server is blocked, or the NAT harvester addresses are missing on a NATed bridge. Both cause ICE failure.
Two people can join but the third breaks the call. The classic symptom of bridge channel problems. Check the colibri websocket URL test above, and check the browser console for colibri-ws connection errors.
Guests cannot join at all. The anonymousdomain in config.js does not exactly match the guest VirtualHost in prosody. The two strings must be identical.
Everything worked until a package upgrade. Jitsi moves fast and config formats occasionally change. Read /var/log/jitsi/jicofo.log and jvb.log first, they are verbose and usually name the problem directly.
For a systematic approach to reading Jitsi logs and isolating which component is failing, I wrote a full guide: How to Troubleshoot Jitsi.
Best Practices
- Give every bridge a unique, boring nickname (
jvb1,jvb2) and keep the nickname,server-id, and nginx path in sync. Mismatches here are the top source of websocket errors. - Restrict port 5222 on the main server to your JVB servers’ IPs with a firewall. Only bridges need XMPP access from outside.
- Monitor bridge load, not just uptime. JVB exposes statistics; watch packet loss and CPU per bridge so you know when to add the next one.
- Keep versions aligned. When you upgrade jitsi-meet on the main server, upgrade
jitsi-videobridge2on every bridge in the same maintenance window. - Load test before you need the capacity. You can simulate real conference load and find your per-bridge limits before launch day.
Conclusion
You now have a production-shaped Jitsi Meet deployment: a main server running the frontend, prosody, and jicofo with secure domain authentication, and a dedicated videobridge server carrying the media, with websockets working across both. Most importantly, you have a scaling path: every additional videobridge is 15 minutes of repeatable work.
For next steps, learn how the bridge websocket behaves in detail in Configure JVB WebSocket, and keep How to Troubleshoot Jitsi bookmarked for the day something misbehaves.