fix(livekit): single UDP media port + pin node_ip for the NAT'd server

The prod box is behind NAT (private 192.168.88.61 behind public 118.95.33.89), so
LiveKit auto-IP-detection would pick the wrong (outbound) address. Pin
rtc.node_ip=118.95.33.89 and collapse media to one UDP port (50000) + TCP 7881 to
minimize the upstream gateway port-forward the network team must add. Docs updated
with the exact forward table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 13:50:24 +05:30
parent 10e393a31f
commit 472170784b
3 changed files with 23 additions and 14 deletions
+15 -8
View File
@@ -213,14 +213,21 @@ The app mints join tokens with the secret (server-side only); the same key/secre
- **Websockets Support: ON**. Request an SSL cert (Let's Encrypt) + Force SSL. - **Websockets Support: ON**. Request an SSL cert (Let's Encrypt) + Force SSL.
- NPM reaches `livekit:7880` by container name — both are on `nginx_proxy_manager_default`. - NPM reaches `livekit:7880` by container name — both are on `nginx_proxy_manager_default`.
**4. Open the media ports on the VPS firewall** (these are host-published by the compose service, **4. Media ports — NAT port-forward (REQUIRED here).** This box sits **behind NAT**: its only
bypassing NPM — WebRTC media can't traverse an L7 proxy): interface is a private `192.168.88.61`; the public `118.95.33.89` (DNS) is mapped by an upstream
```bash gateway. WebRTC media can't traverse NPM (L7), so the gateway/router must forward the media ports
ufw allow 50000:50100/udp # WebRTC media (must match livekit.yaml port range) to the box. To keep the ask minimal, LiveKit is configured for **one** UDP port + one TCP fallback:
ufw allow 7881/tcp # WebRTC-over-TCP fallback for restrictive networks
``` Ask whoever controls the network/gateway to forward, from `118.95.33.89` → `192.168.88.61`:
If the VPS is behind NAT and LiveKit can't auto-detect its public IP, set `rtc.node_ip` in | Port | Proto | Purpose |
`livekit.yaml` to `118.95.33.89` and redeploy. |------|-------|---------|
| 50000 | UDP | WebRTC media (all participants mux over this one port) |
| 7881 | TCP | WebRTC-over-TCP fallback (restrictive client networks) |
`livekit.yaml` already pins `rtc.node_ip: 118.95.33.89` (auto-detect would pick the wrong outbound
IP behind this NAT). The host's local `ufw` is inactive, so no host-firewall change is needed — the
only requirement is the upstream port-forward above. Until it exists, signaling connects but media
won't flow (participants see each other's tiles but no video/audio).
**5. Deploy** (the livekit service is behind a `sfu` compose profile, so it stays dormant on a **5. Deploy** (the livekit service is behind a `sfu` compose profile, so it stays dormant on a
normal deploy — start it explicitly): normal deploy — start it explicitly):
+2 -2
View File
@@ -43,8 +43,8 @@ services:
volumes: volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro - ./livekit.yaml:/etc/livekit.yaml:ro
ports: ports:
- "7881:7881" # WebRTC over TCP (fallback) - "7881:7881" # WebRTC over TCP (fallback)
- "50000-50100:50000-50100/udp" # WebRTC media (UDP) — must match livekit.yaml port range - "50000:50000/udp" # single WebRTC media UDP port (must match livekit.yaml rtc.udp_port)
networks: networks:
- npm - npm
+6 -4
View File
@@ -7,10 +7,12 @@
port: 7880 # signaling (HTTP/WS) — NPM proxies wss://livekit.bizgaze.com -> here port: 7880 # signaling (HTTP/WS) — NPM proxies wss://livekit.bizgaze.com -> here
rtc: rtc:
tcp_port: 7881 # WebRTC-over-TCP fallback (restrictive networks) tcp_port: 7881 # WebRTC-over-TCP fallback (restrictive networks)
port_range_start: 50000 # WebRTC media (UDP) — keep in sync with the published range in compose udp_port: 50000 # SINGLE UDP media port (all participants mux over it) — minimizes
port_range_end: 50100 # the NAT port-forward to one UDP + one TCP port.
use_external_ip: true # discover the public IP for ICE candidates (VPS). Or set node_ip below. # This box sits behind NAT (private 192.168.88.61 behind public 118.95.33.89). Auto-detection
# node_ip: 118.95.33.89 # uncomment + set if use_external_ip can't detect the public IP # would find the wrong (outbound) IP, so pin the inbound public IP clients actually reach.
use_external_ip: false
node_ip: 118.95.33.89
# Embedded TURN over TLS on 443 helps clients on locked-down networks. Left off by default because # Embedded TURN over TLS on 443 helps clients on locked-down networks. Left off by default because
# NPM already owns 443; enable via a dedicated hostname + NPM stream if you need it (see DEPLOY.md). # NPM already owns 443; enable via a dedicated hostname + NPM stream if you need it (see DEPLOY.md).