Who this is for
You already run a reverse proxy (Nginx, Traefik or Caddy) on a server. It owns ports 80 and 443 and your certificates, and you want docker-jitsi-meet to sit behind it like any other app. Or you upgraded to stable-11146 or newer and your existing proxy setup stopped working.
The outcome: Jitsi served at https://meet.example.com through your proxy, signaling over WebSocket working, and audio and video flowing directly to the videobridge.
How it works
A Jitsi meeting uses two very different kinds of traffic, and only one of them can go through an HTTP reverse proxy.
Web and signaling go through the proxy. The web container serves the app and forwards two signaling routes to Prosody: /xmpp-websocket (the default, a WebSocket) and /http-bind (BOSH, the HTTP polling fallback). The handbook names /xmpp-websocket as the one route that needs WebSocket handling. Everything else is plain HTTP to the web container.
Media never goes through the proxy. Audio and video go from browsers straight to the videobridge on 10000/udp, which the jvb service publishes on the host. The handbook is explicit that JVB_ADVERTISE_IPS must be set whether or not you use a reverse proxy, because the advertised IP receives the media and the proxy never sees it. If it is wrong, calls break when a third person joins.
The browser learns its URLs from PUBLIC_URL. The generated config sets config.websocket to wss://<your domain>/xmpp-websocket and config.bosh to https://<your domain>/http-bind. If PUBLIC_URL is unset it falls back to https://localhost:8443, so every visitor’s browser tries to connect to its own machine.
What changed in stable-11146. The web container now listens on unprivileged ports 8000 (HTTP) and 8443 (HTTPS) inside the container, instead of 80 and 443, so it can run without root. The host side did not change: HTTP_PORT and HTTPS_PORT still default to 8000 and 8443, and compose maps them to the new container ports ('${HTTP_PORT}:8000'). Separately, the colibri WebSocket routes /colibri-ws/ and /colibri-relay-ws/ were removed, because the bridge channel now always uses SCTP data channels.
Before you start
- docker-jitsi-meet stable-11146 or newer (the latest is stable-11248), with compose files and image tags from the same release. Coming from an older release? Follow the rootless upgrade guide first.
- A DNS A record for meet.example.com pointing at the server.
- Your proxy holds a valid certificate for meet.example.com. Caddy and Traefik can get one automatically.
- Firewall:
443/tcp(and80/tcpfor redirects) to the proxy,10000/udpto the server. - Your server’s public IP, used below as
203.0.113.10. - Do not serve Jitsi from a subdirectory such as example.com/meet. The handbook warns it does not work well.
Steps
1. Configure .env
In the docker-jitsi-meet folder, edit .env. The first three lines are the handbook’s reverse proxy settings:
DISABLE_HTTPS=1
ENABLE_HTTP_REDIRECT=0
ENABLE_LETSENCRYPT=0
PUBLIC_URL=https://meet.example.com
JVB_ADVERTISE_IPS=203.0.113.10
HTTP_PORT=8000
HTTPS_PORT=8443With DISABLE_HTTPS=1 the container does not start its HTTPS server block at all, so only HTTP on 8000 answers. Keep ENABLE_HTTP_REDIRECT=0, otherwise the container answers plain HTTP with a redirect to https and your proxy loops. Do not put a port in PUBLIC_URL. It must be the address users type.
2. Keep the container ports off the internet
Bind the published web ports to localhost so only the proxy can reach them. Edit the web service in docker-compose.yml:
ports:
- '127.0.0.1:${HTTP_PORT}:8000'
- '127.0.0.1:${HTTPS_PORT}:8443'The official update procedure unzips the new release over your files, so re-apply this edit after every upgrade. Skip this step if your proxy runs in Docker and reaches the container over a shared network (Traefik below).
3. Restart Jitsi
docker compose down
docker compose up -d4a. Nginx on the host
This server block follows the handbook routes, adds forwarded headers, and raises the read timeout. Nginx closes a proxied WebSocket that sends no data for 60 seconds by default. The 900 second value matches the handbook’s Apache example.
server {
listen 80;
server_name meet.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name meet.example.com;
ssl_certificate /etc/letsencrypt/live/meet.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/meet.example.com/privkey.pem;
location = /xmpp-websocket {
proxy_pass http://127.0.0.1:8000$request_uri;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900s;
tcp_nodelay on;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
}sudo nginx -t && sudo systemctl reload nginx4b. Traefik with Docker labels
Traefik supports WebSocket connections with no extra configuration. Create docker-compose.override.yml next to docker-compose.yml. The entrypoint websecure, the resolver letsencrypt and the network proxy are placeholders: use the names from your own Traefik setup.
services:
web:
labels:
traefik.enable: "true"
traefik.docker.network: "proxy"
traefik.http.routers.jitsi.rule: "Host(`meet.example.com`)"
traefik.http.routers.jitsi.entrypoints: "websecure"
traefik.http.routers.jitsi.tls.certresolver: "letsencrypt"
traefik.http.services.jitsi.loadbalancer.server.port: "8000"
networks:
meet.jitsi:
proxy:
networks:
proxy:
external: trueSet the port label explicitly. When a container exposes several ports, Traefik picks the lowest one. That happens to be 8000 on new images (it was 80 on old ones), but an explicit label written for an old release says 80 and breaks after the upgrade.
4c. Caddy
Caddy proxies WebSockets automatically and sets X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host by default. It also gets and renews the certificate for the site name on its own.
meet.example.com {
reverse_proxy 127.0.0.1:8000 {
stream_close_delay 5m
}
}By default Caddy closes open WebSockets when its config reloads. stream_close_delay keeps them open for a while, so a reload does not drop everyone from their meetings. If Caddy runs in Docker on the Jitsi network, use reverse_proxy web:8000 instead.
sudo systemctl reload caddy5. Update a proxy config written for stable-11031 or older
- Any target that points at the container directly (
web:80,web:443, or a Traefik port label of80) must change toweb:8000. - Delete any
/colibri-ws/or/colibri-relay-ws/routes, and theENABLE_COLIBRI_WEBSOCKET,COLIBRI_WEBSOCKET_PORTandCOLIBRI_WEBSOCKET_REGEXvariables. They no longer exist. - Targets that use the host ports (
127.0.0.1:8000) keep working, because the host defaults did not change.
Configuration reference
| Setting | Where | Default | What it does |
|---|---|---|---|
PUBLIC_URL |
.env |
https://localhost:8443 if unset |
Public address; builds the browser’s websocket and BOSH URLs |
DISABLE_HTTPS |
.env |
0 |
1 removes the container’s HTTPS server, for TLS on the proxy |
ENABLE_HTTP_REDIRECT |
.env |
0 |
1 makes container HTTP redirect to https; keep 0 behind a proxy |
ENABLE_LETSENCRYPT |
.env |
off | Built-in certificates; needs host ports 80 and 443, so disable behind a proxy |
HTTP_PORT |
.env |
8000 |
Host port mapped to container port 8000 |
HTTPS_PORT |
.env |
8443 |
Host port mapped to container port 8443 |
JVB_ADVERTISE_IPS |
.env |
unset | IPs the bridge gives browsers for media; set to the public IP |
JVB_PORT |
.env |
10000 |
UDP media port, published directly on the host |
ENABLE_XMPP_WEBSOCKET |
.env |
1 |
0 makes the client fall back to BOSH on /http-bind |
BOSH_RELATIVE |
.env |
false |
Uses a relative /http-bind path instead of the full URL |
Common mistakes
- Joining fails or spins forever.
PUBLIC_URLis unset or has a port, so the browser connects to the wrong address. Set it tohttps://meet.example.comand restart. - 502 Bad Gateway after upgrading. The proxy still targets container port 80. Change it to 8000.
- Redirect loop.
ENABLE_HTTP_REDIRECT=1with TLS on the proxy. Set it to0. - Two people work, the third breaks video. UDP 10000 is blocked or
JVB_ADVERTISE_IPSis wrong. The proxy cannot help here. See video fails with 3 people. - Meetings drop about a minute in. The proxy closes idle WebSockets. Raise
proxy_read_timeoutin Nginx. - Let’s Encrypt errors in the web logs.
ENABLE_LETSENCRYPT=1was left on while the proxy owns port 80. Set it to0.
Verify
- The browser config uses your domain:
curl -s https://meet.example.com/config.js | grep -E "config\.(websocket|bosh) ="Expected:
config.bosh = 'https://meet.example.com/http-bind';
config.websocket = 'wss://meet.example.com/xmpp-websocket';- The WebSocket upgrade passes through the proxy:
curl -s -o /dev/null -w "%{http_code}\n" --http1.1 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: SGVsbG9KaXRzaSE=" \
-H "Sec-WebSocket-Protocol: xmpp" \
https://meet.example.com/xmpp-websocketExpected: 101. A 200, 400 or 502 means the upgrade headers are not reaching Prosody.
-
In the browser, open the developer tools Network tab, filter by WS, join a meeting, and check that
xmpp-websocketshows status101. -
Start a meeting with three people on different networks. If video works for all three, media reaches UDP 10000 directly.
If it still fails
- Read the web container log with
docker compose logs --tail=100 weband look for upstream errors on/xmpp-websocket. - Read Prosody with
docker compose logs --tail=100 prosody. No new connections means traffic stops at the proxy. - Check the container answers locally:
curl -sI http://127.0.0.1:8000/should return200. - If the web container fails with
open() "/run/web/config/nginx/nginx.conf" failed, check you are on a stable release and not master, which uses nightly unstable images. - For media problems, see video fails with 3 people. The proxy is not involved.
What we have not confirmed yet
We checked everything above against the stable-11248 templates and compose file, the Jitsi handbook, the merged pull requests, and the Nginx, Traefik and Caddy documentation on 2026-10-05. We have not yet run every variant on a real server. These points are still open:
- The Traefik labels, the Caddy block and the Nginx timeouts have not been tested on a live stable-11248 server.
- The exact WebSocket curl response from Prosody (101 with the
xmppsubprotocol and no room parameter) needs confirming on a real server. - The handbook’s “Disabling WebSocket connections” section still sets
ENABLE_SCTP=1, but that variable no longer appears in the stable-11248 compose files or templates, since PR #2285 made SCTP always on. The handbook looks out of date there. - The right
/http-bindread timeout depends on the BOSH wait the client requests. 120 seconds is a safe value, not a measured one. - Whether
docker-compose.override.ymlcan remove the default public port mappings, instead of editingdocker-compose.yml, was not checked. - A reverse proxy in front of a Debian/Ubuntu package install, which ships its own Nginx on 443, is not covered here.
Sources
- docker-jitsi-meet PR #2261 refactor(web): use unprivileged ports 8000 and 8443, merged 2026-06-20, maintainer PR
- docker-jitsi-meet release stable-11146, 2026-08-03, release note
- Jitsi handbook, Self-Hosting Guide: Docker (quick start, external ports, Let’s Encrypt, updating, NAT, running behind a reverse proxy), checked 2026-10-05, official doc
- docker-jitsi-meet docker-compose.yml at stable-11248 (web and jvb ports), source code
- docker-jitsi-meet web/rootfs/defaults/meet.conf at stable-11248 (/http-bind and /xmpp-websocket routes), source code
- docker-jitsi-meet web/rootfs/defaults/system-config.js at stable-11248 (PUBLIC_URL, websocket and BOSH URLs), source code
- docker-jitsi-meet web/rootfs/defaults/default at stable-11248 (listen 8000 and 8443, DISABLE_HTTPS, ENABLE_HTTP_REDIRECT), source code
- docker-jitsi-meet PR #2285 remove deprecated colibri websocket support, merged 2026-07-29, maintainer PR
- docker-jitsi-meet env.example at stable-11248, source code
- docker-jitsi-meet issue #2275 nginx.conf not found on master branch, 2026-06-29, community report with maintainer comments
- docker-jitsi-meet release stable-11248, 2026-09-14, release note
- nginx documentation, WebSocket proxying, checked 2026-10-05, official doc
- Caddy documentation, reverse_proxy directive, checked 2026-10-05, official doc
- Caddy documentation, Automatic HTTPS, checked 2026-10-05, official doc
- Traefik documentation, Expose overview (WebSocket support), checked 2026-10-05, official doc
- Traefik documentation, Docker provider (port selection), checked 2026-10-05, official doc
Need a hand?
If Jitsi still will not load through your proxy, contact our engineers with your proxy config, your .env without secrets, and the output of docker compose logs --tail=100 web prosody. Our support plans include reverse proxy setup and testing.