From caf772f76c3291aa8d970575fe4577a06880b7a2 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 29 May 2026 09:22:45 +0200 Subject: [PATCH 1/4] fix: resolve TURN_DOMAIN default at .env layer docker-compose does not recursively expand nested `${...}` in `:-` defaults, so `TURN_DOMAIN=${TURN_DOMAIN:-${LIVEKIT_DOMAIN}}` leaked the literal string `${LIVEKIT_DOMAIN}` into the container, breaking TURN and WebRTC connectivity. Set the default in .env.sample where abra shell-expands it before docker-compose sees it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.sample | 4 +++- compose.yml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.env.sample b/.env.sample index e8f8bb6..949bc09 100644 --- a/.env.sample +++ b/.env.sample @@ -82,7 +82,9 @@ LIVEKIT_NODE_IP= #LIVEKIT_TURN_ENABLED=false ## TURN domain — must resolve to this server's IP. ## Defaults to LIVEKIT_DOMAIN, which works for TURN/UDP setups. -#TURN_DOMAIN=turn.example.com +## NOTE: must be set here, not as a compose-level `:-` default — +## docker-compose does not recursively expand nested `${...}` references. +TURN_DOMAIN=${LIVEKIT_DOMAIN} ## TURN/UDP port (default: 443). Recommended because UDP 443 is rarely ## blocked and doesn't conflict with Traefik's TCP 443. #TURN_UDP_PORT=443 diff --git a/compose.yml b/compose.yml index 13e1bcb..cee575a 100644 --- a/compose.yml +++ b/compose.yml @@ -191,7 +191,7 @@ services: - LIVEKIT_NODE_IP - LIVEKIT_FORCE_TCP=${LIVEKIT_FORCE_TCP:-false} - LIVEKIT_TURN_ENABLED=${LIVEKIT_TURN_ENABLED:-true} - - TURN_DOMAIN=${TURN_DOMAIN:-${LIVEKIT_DOMAIN}} + - TURN_DOMAIN=${TURN_DOMAIN} - TURN_UDP_PORT=${TURN_UDP_PORT:-443} # WebRTC ICE ports must be published directly on the host. # These carry raw RTP media, not HTTP — cannot be proxied through Traefik without extra traefik compose. -- 2.49.0 From 802a96e8496630bbe08c86b77f022500414c3999 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 29 May 2026 10:45:27 +0200 Subject: [PATCH 2/4] docs: document host UDP buffer sysctl tuning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LiveKit's startup warning about UDP receive buffer being too small (default 212992 bytes vs suggested 5000000) is easy to overlook, but under load — especially when many clients are forced through the TURN relay — it causes dtls timeouts on publisher transports and asymmetric black-tile / no-media symptoms. The fix has to be on the host because net.core.rmem_max / wmem_max are read when LiveKit opens its UDP sockets and can't be raised from inside the container. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 921a30e..817fecb 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,39 @@ This recipe publishes ports directly on the host for WebRTC media transport. The Your firewall must allow inbound traffic on these ports. +### Host kernel tuning + +LiveKit logs a warning at startup if the kernel's UDP socket buffers are too small: + +``` +WARN livekit rtcconfig/rtc_unix.go:31 UDP receive buffer is too small for a production set-up {"current": 425984, "suggested": 5000000} +``` + +The Linux default (`net.core.rmem_max = 212992`) is well under what LiveKit needs once +several participants are forced through the TURN relay path. The resulting packet +loss shows up as `dtls timeout: read/write timeout: context deadline exceeded` on +publisher transports, intermittent media stalls, or one peer seeing a black tile +while the other sees video. + +These sysctls are read by LiveKit when it opens its UDP sockets, so they must be +set on the **host** (not in the container) before the LiveKit container starts. + +On the host, create `/etc/sysctl.d/99-livekit.conf`: + +``` +net.core.rmem_max = 7500000 +net.core.wmem_max = 7500000 +``` + +Then apply and restart the service: + +``` +sudo sysctl --system +docker service update --force _livekit +``` + +The warning should be gone from the LiveKit boot log. + ### TURN server TURN is enabled by default and helps users behind CGNAT/symmetric NAT connect to video calls. To disable it, remove `compose.turn.yml` from `COMPOSE_FILE` in your app config and set `LIVEKIT_TURN_ENABLED=false`. -- 2.49.0 From c7f56bf13a0a1535cac66774f7c5950dca9ef4f3 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 29 May 2026 10:49:27 +0200 Subject: [PATCH 3/4] Mention chrome on mobile --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 817fecb..ff4ee0e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ Then redeploy the app, and automated e-mail sending should work: * **One instance per server.** LiveKit requires host-published ports (7881, 7882, 443, 30000-30009) which can only be bound once per host. * **Server must have a direct public IP.** LiveKit's built-in TURN server does not work on servers behind a NAT gateway due to hairpin NAT issues. Configuring hairpin NAT on the gateway may be possible but has not been successfully tested yet. +* **Mobile browser must be Chrome** - there are various open issues wrt Firefox and WebRTC, so on mobile you have +to use a chromium based browser, else the connections fail! ## Network ports -- 2.49.0 From c3b9909b5170b3ddbce44547011c997b4a02de08 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 29 May 2026 10:50:38 +0200 Subject: [PATCH 4/4] Fix table --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff4ee0e..68bc07d 100644 --- a/README.md +++ b/README.md @@ -96,12 +96,10 @@ to use a chromium based browser, else the connections fail! This recipe publishes ports directly on the host for WebRTC media transport. These carry raw RTP media packets and are not routed through Traefik. The WebSocket signaling endpoint (`wss://LIVEKIT_DOMAIN`) is routed through Traefik as normal. -| Port | Protocol | Purpose | -|------|----------|---------| -| 7881 | TCP | WebRTC ICE over TCP (fallback when UDP is blocked) | -| 7882 | UDP | WebRTC ICE over UDP (primary media transport) | -| 443 | UDP | TURN relay (enabled by default via `compose.turn.yml`) | -| 30000-30009 | UDP | TURN relay allocation ports | +- **7881/TCP** — WebRTC ICE over TCP (fallback when UDP is blocked) +- **7882/UDP** — WebRTC ICE over UDP (primary media transport) +- **443/UDP** — TURN relay (enabled by default via `compose.turn.yml`) +- **30000-30009/UDP** — TURN relay allocation ports Your firewall must allow inbound traffic on these ports. -- 2.49.0