From 2f3a489a8ad582396f7b3270966e55a03521d5a7 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Tue, 11 Aug 2026 10:08:42 +0000 Subject: [PATCH] fix: WHIP browser-publish silently never sends media behind Cloudflare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MTX_WEBRTCADDITIONALHOSTS (the ICE host candidate MediaMTX advertises for WebRTC/WHIP) was wired to PUBLIC_HOST, the Cloudflare-proxied domain. The WHIP HTTP handshake (SDP offer/answer through nginx) still succeeds through Cloudflare, so 'stream from this browser' looks like it works — but the actual media is a raw UDP path (port 8189) that Cloudflare never forwards regardless of port, same as the earlier RTMP issue. The browser ends up trying to send video/audio to Cloudflare's edge, which drops it, so nothing ever actually arrives. Split into a dedicated MEDIAMTX_WEBRTC_HOST env var (raw origin IP in production) instead of reusing PUBLIC_HOST, mirroring how MEDIAMTX_RTMP_PUBLIC already does this for the same reason. Confirmed via podman inspect that the previously-deployed container really was resolving MTX_WEBRTCADDITIONALHOSTS to the Cloudflare-proxied hostname before this fix. --- .env.example | 8 ++++++++ docker-compose.yml | 12 ++++++++++-- mediamtx/mediamtx.yml | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 1e95381..9f5c15c 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,14 @@ MEDIAMTX_WHIP_PUBLIC=http://${PUBLIC_HOST}:8889 MEDIAMTX_HLS_PUBLIC=http://${PUBLIC_HOST}:8890 BLOSSOM_URL_DEFAULT=http://${PUBLIC_HOST}:8098 +# ICE host candidate MediaMTX advertises for WebRTC/WHIP (browser-publish +# "stream from this browser"). If you're behind Cloudflare or similar +# HTTP(S)-only proxy, this MUST be the raw origin IP, not PUBLIC_HOST — +# Cloudflare never forwards raw UDP, so a proxied hostname here makes the +# WHIP handshake succeed while media silently never arrives. Same reasoning +# as MEDIAMTX_RTMP_PUBLIC above. Plain host/IP, no scheme or port. +MEDIAMTX_WEBRTC_HOST=${PUBLIC_HOST} + # Default nostr relays for NIP-53 live-event announcements (comma separated, # changeable at runtime in Settings) NOSTR_RELAYS=wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band diff --git a/docker-compose.yml b/docker-compose.yml index 103f511..2bbf4d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,8 +44,16 @@ services: - "8189:8189/udp" # WebRTC ICE - "8890:8888" # HLS (host 8890; 8888 kept free for other apps) environment: - # Browsers need a reachable ICE host candidate; set PUBLIC_HOST in .env - MTX_WEBRTCADDITIONALHOSTS: ${PUBLIC_HOST:-localhost} + # Browsers need a reachable ICE host candidate for the actual UDP media + # path (browser-publish "stream from this browser" / WHIP). This must + # be the raw origin IP, NOT PUBLIC_HOST — Cloudflare's proxy only + # forwards HTTP(S), never raw UDP, regardless of port (same reason + # MEDIAMTX_RTMP_PUBLIC above uses the raw IP instead of the + # Cloudflare-proxied domain). Using PUBLIC_HOST here means the browser + # resolves the ICE candidate to Cloudflare's edge and the WHIP HTTP + # handshake succeeds while media silently never arrives — set + # MEDIAMTX_WEBRTC_HOST in .env. + MTX_WEBRTCADDITIONALHOSTS: ${MEDIAMTX_WEBRTC_HOST:-localhost} volumes: - ./mediamtx/mediamtx.yml:/mediamtx.yml:ro - mediamtx-recordings:/recordings diff --git a/mediamtx/mediamtx.yml b/mediamtx/mediamtx.yml index 8a48890..63df3d3 100644 --- a/mediamtx/mediamtx.yml +++ b/mediamtx/mediamtx.yml @@ -39,6 +39,9 @@ webrtc: yes webrtcAddress: :8889 webrtcLocalUDPAddress: :8189 webrtcAllowOrigins: ["*"] +# webrtcAdditionalHosts is set via MTX_WEBRTCADDITIONALHOSTS in +# docker-compose.yml (MEDIAMTX_WEBRTC_HOST in .env) — see the comment there +# for why it must be the raw IP, not the Cloudflare-proxied domain. # ---- recording ----------------------------------------------------------- pathDefaults: