LiveKit is an open-source WebRTC SFU (selective forwarding unit) for building real-time audio/video/data apps. This guide covers a single-node install on Ubuntu 22.04/24.04 using LiveKit's official install script and Docker Compose.
1. Prerequisites
- A RamNode KVM VPS — 2 vCPU / 2–4 GB RAM for testing/small deployments; scale up (CPU especially) for more concurrent participants, since LiveKit can do SFU forwarding cheaply but simulcast/transcoding features cost more.
- Ubuntu 22.04/24.04, fully updated.
- A domain name pointed at your VPS's public IP (e.g.
livekit.yourdomain.com) — needed for TLS, since browsers require secure contexts for WebRTC. - Docker + Docker Compose (recommended deployment method).
- Root or sudo access.
sudo apt update && sudo apt upgrade -yInstall Docker if not present:
curl -fsSL https://get.docker.com | sudo sh
sudo systemctl enable --now docker2. Understand LiveKit's networking needs
LiveKit needs:
- TCP 443 — HTTPS/WSS signaling (can reuse your reverse proxy).
- TCP/UDP 7881 — WebRTC over TCP fallback (optional but recommended).
- UDP 50000–60000 (configurable range) — actual media (RTP) traffic. This is the big one — it must be open and, critically, not behind carrier-grade NAT, since each participant needs a real UDP port reachable from the internet.
RamNode VPS instances with a public IP work fine for this as long as the firewall allows the UDP range.
3. Generate config and use the official install script (quickest path)
LiveKit provides a script that installs a working single-node setup (Caddy for TLS + LiveKit server) via Docker Compose:
curl -sSL https://get.livekit.io | bashThis installs the livekit-server binary directly on the host (not containerized) along with a systemd service, and prompts for your domain to auto-configure TLS via Caddy. It's the fastest route for a straightforward single-node deployment.
If you prefer full Docker Compose control, use Option B below instead.
4. Option B — Manual Docker Compose setup
Create a project directory:
mkdir -p ~/livekit && cd ~/livekitGenerate an API key/secret pair:
docker run --rm livekit/livekit-server generate-keysSave the output — you'll need the API key and API secret.
Create livekit.yaml:
port: 7880
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 60000
use_external_ip: true
keys:
<API_KEY>: <API_SECRET>Create docker-compose.yaml:
services:
livekit:
image: livekit/livekit-server:latest
command: --config /etc/livekit.yaml
restart: unless-stopped
network_mode: host
volumes:
- ./livekit.yaml:/etc/livekit.yaml
caddy:
image: caddy:2
restart: unless-stopped
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
volumes:
caddy_data:network_mode: host is important here — it lets LiveKit bind the wide UDP media port range directly without Docker's NAT/port-mapping overhead, which is required for the media ports to work reliably.
Create Caddyfile for TLS termination in front of the signaling port:
livekit.yourdomain.com {
reverse_proxy localhost:7880
}Start everything:
sudo docker compose up -d5. Configure the firewall
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp # for ACME HTTP-01 challenge
sudo ufw allow 7881/tcp
sudo ufw allow 7881/udp
sudo ufw allow 50000:60000/udp
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw statusMirror these in RamNode's cloud firewall/security groups. The UDP range is large (10,000 ports) — make sure your firewall rule allows the full range, not just a few ports.
6. Verify the deployment
Check logs:
sudo docker compose logs -f livekitOr, if you used the install-script (non-Docker) path:
sudo systemctl status livekit
sudo journalctl -u livekit -fCheck connectivity with LiveKit's CLI:
curl -s https://livekit.yourdomain.comYou should get a response from the LiveKit HTTP server (not a connection error).
7. Test with a real room
Install the LiveKit CLI:
curl -sSL https://get.livekit.io/cli | bashConfigure it:
lk project add --url wss://livekit.yourdomain.com --api-key <API_KEY> --api-secret <API_SECRET> --name productionCreate a test token and join a room from the LiveKit Meet demo app (meet.livekit.io), pointing it at your server URL and a generated token, to confirm audio/video flow end-to-end.
8. Common troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Client connects but no media flows | UDP port range blocked by firewall — this is the #1 cause; double-check both ufw and any cloud-level security group |
| TLS/connection refused on wss:// | Caddy not running or DNS not pointed correctly — check docker compose logs caddy |
| Works locally, fails for remote users | use_external_ip: true missing, or VPS behind NAT without a truly public IP — confirm with curl ifconfig.me and compare to what's in DNS |
| High CPU under load | Simulcast/dynacast settings, or too many rooms per node — check docker stats and consider scaling out |
9. Security notes
- Keep your API secret private — it's used to mint access tokens server-side; never embed it in client apps.
- Consider rotating keys periodically via
livekit-server generate-keysand updating both server config and your token-issuing backend. - If exposing the LiveKit HTTP API beyond signaling, put it behind auth/rate-limiting at the Caddy layer.
10. Next steps
- Set up LiveKit Egress (separate service) for recording/streaming rooms to file or RTMP.
- Set up LiveKit Ingress if you need to bring in RTMP/WHIP streams.
- For higher scale, look into multi-node LiveKit with Redis for coordination (documented in LiveKit's clustering guide).
- Back up
livekit.yaml, your Caddyfile, and your API keys somewhere secure outside the VPS.
