How do I self-host LiveKit?

Run the livekit-server Docker image alongside Redis for multi-node state and a coturn TURN server for NAT traversal. For production scale, deploy via the official LiveKit Helm chart on Kubernetes.

Does LiveKit need a TURN server?

Yes, for production reliability. Without TURN, users behind symmetric NATs or restrictive corporate firewalls — typically 20–40% of real-world network conditions — will experience failed connections.

Self-Hosted LiveKit: Complete Deployment Guide 2026

From a single Docker Compose stack to a Kubernetes cluster serving thousands of concurrent users — here's the production deployment path for self-hosted LiveKit.

Beyond the base topology, this guide covers security hardening, backup and disaster recovery, load testing before you trust a capacity estimate, a safe upgrade strategy, and egress/recording infrastructure — the operational depth that separates a working demo deployment from one you can responsibly run in production.

By Kaushik Parmar·17 min read·July 6, 2026

Production Deployment Topology

Load Balancerlivekit-server 1livekit-server 2livekit-server 3Redis (room state)coturn TURN

Choosing Between Self-Hosting and LiveKit Cloud First

Before working through this deployment guide, it's worth confirming self-hosting is actually the right call for where your product is today. LiveKit Cloud runs the identical open-source technology with zero infrastructure to operate, and for early-stage products still validating usage patterns, the operational simplicity is usually worth more than the cost savings self-hosting eventually provides. This guide is written for teams who've either already validated real usage and are migrating to self-hosted for cost or data-sovereignty reasons, or who have existing infrastructure operations capability and want to start there from day one — if neither describes your situation yet, LiveKit Cloud is very likely the better starting point, with self-hosting available as a well-trodden migration path once you actually need it.

Single-Node: Docker Compose

# docker-compose.yml
services:
  livekit:
    image: livekit/livekit-server:latest
    command: --config /etc/livekit.yaml
    ports:
      - "7880:7880"       # WebSocket signalling
      - "7881:7881"       # TCP fallback
      - "50000-60000:50000-60000/udp"  # RTP media range
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml
    depends_on: [redis]

  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]

  coturn:
    image: coturn/coturn:latest
    network_mode: host
    volumes:
      - ./turnserver.conf:/etc/coturn/turnserver.conf

This single-node stack comfortably handles up to ~200 concurrent users on a $60/month t3.xlarge-class instance — sufficient for most early-stage products.

Sizing Your First Server Correctly

The commonly cited "$60/month handles ~200 concurrent users" figure assumes a t3.xlarge-class instance (4 vCPU, 16GB RAM) running audio-heavy or small-group-video workloads. If your product is video-first with larger group calls, size up — video encoding/forwarding is meaningfully more CPU-intensive per participant than audio, and a room with 10+ video participants consumes disproportionately more SFU resources than the same room count with audio only. Start with a slightly larger instance than the minimum estimate for your first production deployment, since resizing a running node is far less disruptive than discovering mid-launch that your initial sizing was too conservative for actual usage patterns.

Why Redis Is Required Beyond One Node

A single LiveKit node keeps room and participant state in memory. The moment you run more than one node for redundancy or scale, that state must be shared — Redis is LiveKit's coordination layer for exactly this, tracking which node owns which room so participants connecting to different nodes still join the same session correctly.

Scaling Up: Kubernetes via the Official Helm Chart

helm repo add livekit https://helm.livekit.io
helm install livekit livekit/livekit-server \
  --set redis.address=my-redis:6379 \
  --set turn.domain=turn.yourdomain.com \
  --set replicaCount=3

Kubernetes deployment adds rolling updates without downtime, horizontal pod autoscaling tied to CPU/participant-count metrics, and health checks that automatically replace unhealthy nodes — the operational maturity that justifies the additional complexity once traffic passes a single-node ceiling.

Configuring livekit.yaml Correctly

Beyond the Docker Compose ports, LiveKit's own livekit.yaml config file controls the settings most likely to bite a first-time deployment: the rtc.port_range_start/end must match whatever UDP port range you've opened in your firewall/security group (the 50000–60000 range in the compose example above), rtc.use_external_ip needs to be enabled and correctly resolving your server's actual public IP for clients behind NAT to connect reliably, and the keys section holding your API key/secret pairs should be populated from environment variables or a secrets manager, never committed directly into the YAML file in version control. Misconfiguring the external IP setting specifically is one of the most common reasons a self-hosted deployment works perfectly from the same network as the server but fails for real users connecting from elsewhere.

TURN Server Integration

LiveKit needs a TURN server for the same reason any WebRTC deployment does — roughly 20–40% of real-world connections sit behind symmetric NATs or restrictive firewalls that block direct peer connections. See CelloIP's dedicated WebRTC TURN server production guide for full coturn configuration, TLS setup, and the cost comparison against cloud TURN services — the same coturn deployment pattern applies directly to a LiveKit stack.

Monitoring

  • LiveKit exposes Prometheus-compatible metrics out of the box — room count, participant count, packet loss, and bandwidth
  • Build Grafana dashboards tracking per-node CPU and participant load to catch capacity issues before users notice
  • Alert on packet loss and jitter thresholds, not just uptime — a 'healthy' node can still deliver poor call quality under load

Security Hardening

A default LiveKit deployment following the quick-start guide is not production-hardened out of the box. Before exposing it to real users:

  • Never use LiveKit's default or example API keys — generate strong, unique API key/secret pairs and store the secret in a proper secrets manager, not in a config file committed to version control
  • Terminate TLS at the load balancer or LiveKit itself for all signalling traffic — an unencrypted WebSocket signalling connection exposes room names and participant identities to anyone on the network path
  • Set short token expiry times for access tokens issued per session, and never issue a long-lived token for anything beyond internal testing
  • Restrict the Redis instance backing multi-node deployments to a private network only — Redis has no authentication by default in many configurations, and an exposed instance is a direct path to full room-state compromise
  • Keep coturn's shared secret rotated on a schedule and restrict its listening ports to only what's needed, since an open TURN relay can be abused for unrelated traffic relay if misconfigured

Backup and Disaster Recovery

LiveKit's room and participant state is inherently ephemeral — a call in progress has no meaningful "backup" the way a database record does, so disaster recovery planning for a LiveKit deployment centers on infrastructure recovery speed, not data recovery. Keep your Docker Compose files, Kubernetes manifests, and coturn/livekit configuration in version control so a full environment can be rebuilt from scratch on new infrastructure within minutes, not hours. If you're using LiveKit's Egress API for recording, that recorded media does need a real backup strategy — treat recordings in S3-compatible storage with the same durability and versioning policies you'd apply to any other business-critical stored asset, since a recording, once a call has ended, cannot be regenerated.

Load Testing Before You Trust a Capacity Estimate

The "~200 concurrent users per $60/month node" figure quoted throughout this guide (and elsewhere) is a reasonable starting estimate, not a guarantee for your specific workload — actual capacity depends heavily on your typical room size (a room with 2 participants uses far less SFU CPU than one with 20), video resolution and bitrate settings, and whether simulcast is enabled. Before committing to a capacity plan, use LiveKit's load-testing tooling (or a custom script using the server SDKs to spin up synthetic bot participants) to simulate your actual expected room-size distribution at target concurrency, and watch CPU, memory, and network utilization on the node under that specific load — not a generic benchmark that may not reflect your product's real usage pattern.

Upgrade Strategy

LiveKit ships regular releases, and staying current matters for both new features and security patches — but upgrading a media server in production without disrupting active calls requires a deliberate strategy, not just a blind image update. On Kubernetes, use rolling updates configured to drain a node (stop accepting new room assignments, wait for existing calls on it to end naturally) before terminating it, rather than a hard restart that would drop in-progress calls. On a single-node Docker Compose deployment, upgrades inherently require a maintenance window, since there's no second node to shift traffic to — this is itself a strong argument for moving to at least a 2-node setup once your product has real users who'd be disrupted by planned downtime.

Egress and Recording Infrastructure

If your product needs call recording or RTMP live-streaming output, LiveKit's Egress service runs as a separate deployable component from the core livekit-server, and needs its own capacity planning — each active egress session consumes meaningful CPU for encoding, roughly comparable to a participant's worth of media processing. Deploy Egress as its own scalable service (it also has an official Helm chart) rather than co-locating it with your core SFU nodes, so a spike in recording demand doesn't compete with your live call capacity for the same server resources.

FAQ

Can I run LiveKit and my TURN server on the same machine?

For small deployments, yes — the Docker Compose example above does exactly that. For production scale, running coturn on separate infrastructure gives cleaner bandwidth accounting and independent scaling of media relay capacity.

How do I know when to move from Docker Compose to Kubernetes?

Once you need more than one LiveKit node for redundancy, or your single-node CPU/bandwidth is consistently near capacity, it's time. Most teams make this move around 500–1,000 concurrent users.

How do I upgrade LiveKit without dropping active calls?

On Kubernetes, use rolling updates with a drain step that stops routing new rooms to a node before terminating it, letting existing calls on that node finish naturally. On single-node deployments, a maintenance window is unavoidable unless you add a second node first.

Does LiveKit's Egress service need separate infrastructure?

Yes for any meaningful scale — Egress consumes significant CPU per active recording/streaming session, comparable to a participant's media processing load, and should run as its own scalable service rather than sharing resources with your core SFU nodes.

How should I load test before trusting a capacity number?

Simulate your actual expected room-size distribution and video settings using bot participants via LiveKit's server SDKs, and monitor real CPU/memory/network utilization under that specific load — generic published capacity figures are a starting estimate, not a guarantee for your specific usage pattern.

What's the most common misconfiguration in a first deployment?

Incorrect or missing rtc.use_external_ip configuration in livekit.yaml — it works fine when testing from the same network as the server, then fails for real remote users, because clients can't correctly discover the server's actual public-facing address for media routing.

Should I self-host LiveKit if I'm still validating my product?

Generally no — LiveKit Cloud runs the identical technology with zero infrastructure to operate, and is the better starting point until you've validated real usage patterns and have a concrete cost or data-sovereignty reason to migrate to self-hosted.

Ongoing Operational Cadence

A production LiveKit deployment isn't a set-and-forget system — plan for a recurring operational rhythm: weekly review of monitoring dashboards for capacity trends before they become incidents, monthly review of LiveKit release notes to decide whether a version upgrade is warranted, quarterly review of your TURN server's bandwidth costs and coturn credential rotation, and an incident postmortem process for any production outage so the same failure mode doesn't recur silently. Teams that treat self-hosted LiveKit as a one-time deployment project rather than an ongoing service they operate are the ones who end up surprised by outages that proper monitoring and cadence would have caught early.

Summary

A single Docker Compose stack gets a self-hosted LiveKit deployment running in an afternoon and comfortably serves early-stage products. The real engineering investment is everything this guide covers beyond that initial stack: security hardening, TURN server sizing, load testing against your actual usage pattern, a safe upgrade strategy, and the ongoing operational cadence that keeps a production deployment healthy rather than one incident away from an outage nobody saw coming.

Need Help Operating Self-Hosted LiveKit?

CelloIP builds AND operates production LiveKit infrastructure — the 2am pager included.