DEV Community

dceniac
dceniac

Posted on

An 8-vCPU, 32 GB server choked on a 3-person 1080p LiveKit call — the bottleneck was outbound bandwidth

I recently added self-hosted video meetings to an internal product using LiveKit on Docker.

The VM looked generous on paper: 8 vCPUs, 32 GB RAM, and a fixed 10 Mbps public bandwidth plan.

In practice, 1080p worked fine with two people in a room. The moment a third person joined with their camera on, video froze.

Here is what happened, why I picked LiveKit **over **Jitsi, and the bandwidth math I should have done before buying a fixed-bandwidth VM.

Why LiveKit instead of Jitsi

Before deploying, I compared Jitsi + Jibri against LiveKit Server + Egress, specifically looking at protocol design and recording:

  • Jitsi + Jibri: Jibri records a room by spinning up a headless Chrome instance to render the meeting page and capturing it with ffmpeg. The output looks just like a real meeting, but one Jibri instance only records one meeting at a time—scaling concurrent recordings means running one Jibri per active recorded room on top of the XMPP stack.
  • LiveKit: Uses WebSocket signaling + WebRTC SFU, separates media routing (livekit-server) from recording/streaming (livekit/egress), and has an official agents framework if you later add voice AI. Note that LiveKit's full-room composite Egress also runs headless Chrome inside its container (requiring SYS_ADMIN capability and roughly 2–6 vCPUs per active recording), and one Egress instance still records one room at a time—so you still size Egress per concurrent recorded room, without carrying the rest of Jitsi's stack.

Deployment was smooth (if you stick to official templates)

Getting LiveKit Server and Egress running via Docker was straightforward. One takeaway if you use coding agents to help wire up configs: anchor strictly on LiveKit's official config generator (livekit/generate) and sample templates. Letting an LLM improvise WebRTC/TURN/Caddy configs from scratch is a fast way to break UDP media routing (50000-60000/udp, 3478/udp, 7881/tcp) or TLS certificates.

Where it actually broke: SFU outbound math

LiveKit is a Selective Forwarding Unit (SFU). Every participant uploads their camera stream once to the server, and the server forwards separate copies down to everyone else in the room.

If everyone has a 1080p camera on at ~4 Mbps (ignoring simulcast downscaling for a worst-case ceiling), the server's outbound (egress) bandwidth grows as N × (N - 1) × 4 Mbps:

Participants (all cameras on) Server outbound bandwidth
1 publishing, 1 watching ~4 Mbps
2 people (1-on-1) ~8 Mbps
3 people ~24 Mbps
4 people ~48 Mbps
10 people ~360 Mbps

On a cloud VM with a 10 Mbps outbound cap:

  • A 1-on-1 call uses ~8 Mbps—just under the ceiling.
  • A 3rd person pushes outbound demand to ~24 Mbps—instant packet loss and frozen frames, while the 8 vCPUs and 32 GB RAM sit mostly idle.

Before going to production, LiveKit's CLI includes lk load-test to simulate publishers and subscribers against your actual box instead of guessing.

Takeaway: bill video servers by metered traffic, not fixed bandwidth

For self-hosted video meetings, outbound network cap matters long before CPU or RAM.

Traffic for meetings is spiky: heavy while rooms are active, near zero the rest of the day. Paying for a high fixed-Mbps pipe 24/7 is expensive, while a cheap 10 Mbps fixed pipe chokes on the third caller. Using pay-per-GB (metered egress traffic) with a high burst bandwidth ceiling fits SFU workloads much better than a flat low-bandwidth cap.


I maintain a plain-English checklist of self-hosting requirements, license terms, and real server/cloud costs for LiveKit and 18 other open-source business apps at BuilderStack.

Top comments (0)