Screen sharing relay
The screen-share relay (technically a Selective Forwarding Unit, or SFU) is a small service inside the server that takes one screen- share upload from a broadcaster and fans it out to every viewer without the broadcaster doing extra work.
Without the relay, screen sharing still works on a local network, but falls back to peer-to-peer, which is unreliable behind strict NAT or across the internet.
When you want it
Section titled “When you want it”- You have more than two or three viewers for a typical share.
- Your users are spread across the internet.
- Some users sit behind corporate NAT or carrier-grade NAT.
If you only ever do 1-on-1 calls on the same network, you may not need it.
Turn it on
Section titled “Turn it on”ports: - "10000:10000/udp"environment: MUMBLE_CONFIG_WEBRTCSFUENABLED: true MUMBLE_CONFIG_WEBRTCSFUPORT: 10000 MUMBLE_CONFIG_WEBRTCSFUPUBLICIP: "203.0.113.5"| Key | Default | Description |
|---|---|---|
webrtcsfuenabled | false | Master toggle. |
webrtcsfuport | 10000 | UDP port the relay listens on. |
webrtcsfupublicip | 127.0.0.1 | Address that viewers use to reach the relay. Must be reachable. |
webrtcsfumodulepath | (built-in) | Override if you want a custom relay binary. |
Networking checklist
Section titled “Networking checklist”- UDP port 10000 is open in the firewall (or whatever you set
webrtcsfuportto). - UDP port 10000 is forwarded from your router to the server (home setups).
- The public IP your server advertises matches the one the
internet can reach. Check with
curl -s https://ifconfig.mefrom the server.
Verify it is working
Section titled “Verify it is working”After a restart, the logs should include:
webrtc-sfu: listening on 0.0.0.0:10000/udp (public 203.0.113.5)Then ask two clients to test:
- Client A starts a screen share.
- Client B should see a thumbnail in the stream grid.
- If B sees a black thumbnail, the data path is broken. Check the public IP and the firewall first.
How the pieces talk to each other
Section titled “How the pieces talk to each other”- The broadcaster’s app uploads one stream to the relay over UDP.
- The relay fans the stream out to every viewer independently.
- All the signaling (ICE negotiation, offer/answer) travels over the existing TCP voice/control port. The relay only carries media.
- The broadcaster’s upload bandwidth stays constant regardless of viewer count - that is the whole point.
Optional: behind a reverse proxy (nginx / Traefik / Caddy)
Section titled “Optional: behind a reverse proxy (nginx / Traefik / Caddy)”A reverse proxy in front of the server is a common setup for TLS termination or port consolidation. Two ports are involved and they behave differently:
| Port | Protocol | Proxy support |
|---|---|---|
| 64738 (voice/control) | TCP | Standard TCP/HTTP proxy |
| 10000 (SFU media) | UDP | Needs explicit UDP forwarding |
The TCP side (signaling) passes through any standard TCP proxy with no special config. The UDP side requires the proxy to support raw UDP forwarding - not every proxy does by default.
nginx’s stream {} block handles raw TCP and UDP. Add it alongside
your http {} block in nginx.conf:
stream { server { listen 10000 udp; # Replace with the address of the host running mumble-server. proxy_pass 127.0.0.1:10000; proxy_timeout 10s; proxy_responses 1; }}Tell the SFU to advertise the proxy’s public IP, not the server’s internal address:
environment: MUMBLE_CONFIG_WEBRTCSFUPUBLICIP: "203.0.113.5" # proxy public IPThe http {} side (TLS termination, WebSocket for the control port)
uses a standard proxy_pass directive and is not shown here.
Traefik supports UDP entry points natively. Add a UDP entry point in
traefik.yml:
entryPoints: sfu-udp: address: ":10000/udp"Then label the mumble-server container so Traefik routes it:
labels: - "traefik.udp.routers.sfu.entrypoints=sfu-udp" - "traefik.udp.services.sfu.loadbalancer.server.port=10000"Set the advertised IP to the Traefik host’s public address:
environment: MUMBLE_CONFIG_WEBRTCSFUPUBLICIP: "203.0.113.5"The standard Caddy binary does not support raw UDP proxying. The
community layer4 plugin adds it:
{ layer4 { :10000 { @sfu udp route @sfu { proxy 127.0.0.1:10000 } } }}You need a custom Caddy build that includes the layer4 module.
For most setups nginx or Traefik are simpler choices for UDP.
Without the relay
Section titled “Without the relay”If webrtcsfuenabled=false (or the library is missing), the app
falls back to direct peer-to-peer for screen sharing:
- Works fine on a local network.
- Works for two clients with no NAT.
- Often fails behind strict NAT.
- Broadcaster’s upload scales linearly with viewer count.
Capacity planning
Section titled “Capacity planning”A modest virtual server can host a relay for a few dozen viewers. For heavier use:
- Each 720p stream is about 1 to 2 Mbps. Multiply by viewer count.
- A 1 vCPU virtual server can handle around 20 to 30 viewer streams.
- For a busy server, run the relay on a separate node and point
webrtcsfupublicipat that node.
TURN servers (advanced)
Section titled “TURN servers (advanced)”If your users sit behind extremely strict firewalls (some corporate networks, mobile carriers), even the relay may not be reachable on UDP 10000. The next step is a TURN server that relays media over 443/TCP. Fancy Mumble does not yet ship a built-in TURN, but you can point clients at any standard TURN by setting their STUN/TURN config.
Disabling
Section titled “Disabling”environment: MUMBLE_CONFIG_WEBRTCSFUENABLED: falseExisting streams stop immediately on restart. Clients fall back to peer-to-peer.
Pitfalls
Section titled “Pitfalls”- Black thumbnails: public IP is wrong or unreachable.
- Logs say “library not found”: the SFU binary was not built into your image. Either rebuild with the relay enabled (see Building from source) or use the official image.
- One-way streams: ICE could not pick a candidate. Check the client’s WebRTC stats from Expert mode.
Next step
Section titled “Next step”Continue with File server.