WebRTC SFU
    Real-time Media

    Deploy LiveKit on a VPS

    Self-host LiveKit, an open-source WebRTC SFU for real-time audio, video, and data apps, on a RamNode VPS via Docker Compose or the official installer with Caddy TLS.

    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.
    shell
    sudo apt update && sudo apt upgrade -y

    Install Docker if not present:

    shell
    curl -fsSL https://get.docker.com | sudo sh
    sudo systemctl enable --now docker

    2. 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:

    shell
    curl -sSL https://get.livekit.io | bash

    This 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:

    shell
    mkdir -p ~/livekit && cd ~/livekit

    Generate an API key/secret pair:

    shell
    docker run --rm livekit/livekit-server generate-keys

    Save the output — you'll need the API key and API secret.

    Create livekit.yaml:

    shell
    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:

    shell
    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:

    shell
    livekit.yourdomain.com {
      reverse_proxy localhost:7880
    }

    Start everything:

    shell
    sudo docker compose up -d

    5. Configure the firewall

    shell
    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 status

    Mirror 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:

    shell
    sudo docker compose logs -f livekit

    Or, if you used the install-script (non-Docker) path:

    shell
    sudo systemctl status livekit
    sudo journalctl -u livekit -f

    Check connectivity with LiveKit's CLI:

    shell
    curl -s https://livekit.yourdomain.com

    You should get a response from the LiveKit HTTP server (not a connection error).

    7. Test with a real room

    Install the LiveKit CLI:

    shell
    curl -sSL https://get.livekit.io/cli | bash

    Configure it:

    shell
    lk project add --url wss://livekit.yourdomain.com --api-key <API_KEY> --api-secret <API_SECRET> --name production

    Create 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

    SymptomLikely cause / fix
    Client connects but no media flowsUDP 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 usersuse_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 loadSimulcast/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-keys and 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.