# How do you scale a WebRTC SFU on Kubernetes?

A selective forwarding unit is a media router: it receives each participant's streams once and forwards them to everyone else, so it scales on bandwidth and stream count rather than CPU alone. Running one on Kubernetes means giving it a real network, placing it near users, and scaling it on the right signal.

Canonical: https://kitecorp.org/guides/scaling-webrtc-sfu-on-kubernetes
Updated: 2026-10-12

## What does an SFU need from the network?

A public address the client can reach directly and a wide range of UDP ports, which is the opposite of what the default Kubernetes pod network provides. The pod network translates addresses and expects a small set of declared ports; an SFU wants to advertise the node's public IP in its ICE candidates and receive media on thousands of ephemeral UDP ports. The usual answer is host networking (`hostNetwork: true`) on a dedicated node pool with a security group that opens the UDP range, and one SFU pod per node. A NodePort service per port is not an option at that scale, and a cloud load balancer in the media path adds latency and cost for no benefit.

## How do you place SFU pods on Kubernetes?

On their own node pool, tainted so nothing else lands there, with one pod per node (a DaemonSet or a Deployment with an anti-affinity rule). The nodes are chosen for network throughput, not CPU: an SFU's limit is outbound bandwidth per node, and cloud instance families differ by an order of magnitude in network allowance. Place pools in the regions where participants are; a call between two people in Frankfurt should not route through Virginia. The signalling service that assigns participants to an SFU is where the placement logic lives, and it should prefer the SFU nearest the majority of a room's participants.

## Where does TURN live?

Close to the users and outside the cluster's media pool. A TURN relay carries the media of participants whose networks block direct UDP, which on corporate and mobile networks is a meaningful share; every relayed stream pays the relay's latency and its egress bill. Run TURN on small nodes in every region you serve, with a public address each, and make the relay the fallback in the ICE policy rather than the default. Watch relayed bandwidth as a cost metric from day one: it is billed per gigabyte by every provider and a sudden rise in relay usage usually means a network path broke, not that usage grew.

## Which signal do you autoscale on?

Outbound bandwidth per node, with stream count as a guard. CPU is misleading for an SFU because forwarding is cheap per packet and the node saturates its network allowance long before its cores. Export each SFU's forwarded stream count and bytes per second, scale the node pool when the fleet's headroom falls below the bandwidth of one node, and never scale a node down while it hosts a live room; rooms are moved by draining (stop assigning new rooms, wait for the last to end) rather than by eviction. Scale-up takes minutes because a node has to join and the SFU has to advertise itself, so the headroom is sized to the room creation rate, not to the average.

## How do you test a media path?

With real media, not synthetic packets. Headless browser clients publishing a looped video and audio track, distributed across regions so the ICE paths and the TURN fallback are exercised, joining rooms at the rate a launch or an event produces. Measure what the user would see: packet loss, jitter and round-trip time per stream, and the time from joining to first frame. Push until a node's bandwidth saturates and confirm the scaler reacts before loss appears. Then kill a node during the test: the signalling layer's reconnection path is the one that fails in production.

Kitecorp runs this work as part of [real-time infrastructure](/services/real-time-infrastructure) engagements.

## How to start

Send a message at https://kitecorp.org/contact or email admin@kitelang.cloud. Pricing follows a discovery call.
