Camera NVR
    WireGuard

    Deploy Frigate NVR on a VPS

    Self-host Frigate NVR 0.17 on a RamNode VPS — CPU object detection for a small camera fleet, WireGuard back to the camera LAN, Caddy TLS, and tuned recording retention.

    Frigate is an open source network video recorder (NVR) built around real time AI object detection for IP cameras. This guide covers a production deployment of the current stable release (0.17) on a RamNode KVM VPS running Ubuntu 24.04 LTS, with host hardening, a reverse proxy with automatic TLS, and a backup plan.

    Read this before you start: VPS suitability

    Frigate is designed to run on bare metal with low overhead access to a Google Coral, Hailo, or a GPU for accelerated inference. A standard RamNode VPS has none of that hardware, so object detection runs on the CPU. That is workable, but it sets two hard constraints on the design:

    1. Keep the camera count and detection resolution low. CPU inference is several times slower than a Coral. Plan for one to three cameras detecting at 1280x720 or lower. Recording can still be full resolution; only the detect stream needs to be small.
    2. Do not expose your cameras to the public internet. Cameras live on a private LAN. The correct pattern is a WireGuard tunnel from the VPS back to the camera network so Frigate pulls RTSP over an encrypted link. Port forwarding camera RTSP straight to the internet is a serious security mistake and is out of scope here.

    If you need heavy multi camera AI, Frigate belongs on a box with a Coral or GPU, not a budget VPS. For a handful of cameras with motion and basic person/car detection, a VPS deployment is reasonable.

    Recommended RamNode sizing

    WorkloadvCPURAMDisk
    1 to 2 cameras, CPU detection44 GB80 GB+
    3 cameras, CPU detection4 to 68 GB160 GB+

    Recordings dominate disk use. Budget roughly 5 to 15 GB per day per 1080p camera on motion only recording, and around 40 GB per day for continuous recording. Tune retention to your disk.

    Prerequisites

    • A RamNode KVM VPS with Ubuntu 24.04 LTS installed.
    • A domain or subdomain (for example frigate.example.com) with an A record pointing at the VPS public IP. TLS certificates depend on this.
    • SSH access as root or a sudo user.
    • A reachable camera network. This guide assumes cameras are reachable over a WireGuard tunnel or private network at addresses like 10.10.0.x.

    Step 1: Host hardening

    Log in and create a non-root sudo user.

    shell
    adduser deploy
    usermod -aG sudo deploy
    rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

    From your workstation, confirm you can log in as deploy with your SSH key, then lock down SSH. Edit /etc/ssh/sshd_config:

    shell
    PermitRootLogin no
    PasswordAuthentication no

    Reload SSH:

    shell
    sudo systemctl reload ssh

    Install and configure the firewall. Frigate sits behind a reverse proxy, so only SSH, HTTP, and HTTPS are public.

    shell
    sudo apt update && sudo apt -y install ufw fail2ban
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable

    Enable unattended security updates:

    shell
    sudo apt -y install unattended-upgrades
    sudo dpkg-reconfigure --priority=low unattended-upgrades

    fail2ban ships with a sensible SSH jail enabled by default once installed.

    Step 2: Install Docker

    shell
    sudo apt -y install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    sudo apt update
    sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    sudo usermod -aG docker deploy

    Log out and back in so the group change takes effect.

    Step 3: Lay out the Frigate directories

    shell
    sudo mkdir -p /var/lib/frigate/config
    sudo mkdir -p /var/lib/frigate/storage
    sudo chown -R deploy:deploy /var/lib/frigate

    Step 4: Write the Frigate config

    Create /var/lib/frigate/config/config.yml. This is a minimal CPU detection config for two cameras. Adjust the camera names, RTSP paths, and the detect resolution to match your hardware.

    shell
    mqtt:
      enabled: false
    
    detectors:
      cpu1:
        type: cpu
        num_threads: 3
    
    ffmpeg:
      # input_args presets reduce CPU on the decode side
      output_args:
        record: preset-record-generic-audio-copy
    
    record:
      enabled: true
      retain:
        days: 7
        mode: motion
      alerts:
        retain:
          days: 14
      detections:
        retain:
          days: 14
    
    snapshots:
      enabled: true
      retain:
        default: 14
    
    objects:
      track:
        - person
        - car
    
    cameras:
      front_door:
        enabled: true
        ffmpeg:
          inputs:
            - path: rtsp://user:password@10.10.0.21:554/stream1
              roles:
                - record
            - path: rtsp://user:password@10.10.0.21:554/stream2
              roles:
                - detect
        detect:
          width: 1280
          height: 720
          fps: 5
      driveway:
        enabled: true
        ffmpeg:
          inputs:
            - path: rtsp://user:password@10.10.0.22:554/stream1
              roles:
                - record
            - path: rtsp://user:password@10.10.0.22:554/stream2
              roles:
                - detect
        detect:
          width: 1280
          height: 720
          fps: 5

    A few notes:

    • Most cameras expose a high resolution main stream and a low resolution sub stream. Use the main stream for record and the sub stream for detect. This is the single most important tuning step on a CPU only host.
    • Keeping fps low (5 is plenty for detection) sharply reduces CPU load.
    • The built in cpu detector works out of the box but is slow. Once the deployment is stable, consider switching to the OpenVINO detector running on CPU, which is more efficient. See the Frigate object detector documentation for the OpenVINO model configuration.

    Step 5: Create the Compose file

    Create /var/lib/frigate/docker-compose.yml. Note that the hardware device passthrough lines from the upstream sample are deliberately omitted, since a VPS has no Coral or GPU. Port 5000 (the unauthenticated internal API) is intentionally not published; only the authenticated UI on 8971 is exposed to the reverse proxy.

    shell
    services:
      frigate:
        container_name: frigate
        image: ghcr.io/blakeblackshear/frigate:stable
        restart: unless-stopped
        stop_grace_period: 30s
        shm_size: "256mb"  # raise this if you add cameras, see the shm formula below
        volumes:
          - /etc/localtime:/etc/localtime:ro
          - /var/lib/frigate/config:/config
          - /var/lib/frigate/storage:/media/frigate
          - type: tmpfs
            target: /tmp/cache
            tmpfs:
              size: 1000000000
        ports:
          - "127.0.0.1:8971:8971"  # authenticated UI, bound to localhost for the reverse proxy
        environment:
          FRIGATE_RTSP_PASSWORD: "changeme"

    Binding port 8971 to 127.0.0.1 means it is only reachable by the reverse proxy on the same host, never directly from the internet.

    Sizing shm

    The default shm of 128 MB suits two cameras at 720p. For more or higher resolution cameras, compute the minimum with the formula from the Frigate docs:

    shell
    python3 -c 'print("{:.2f}MB".format(((1280 * 720 * 1.5 * 20 + 270480) / 1048576) * 2 + 40))'

    Set shm_size above the result, then recreate the container with docker compose down && docker compose up -d (a plain restart does not apply a new shm size).

    Step 6: Start Frigate

    shell
    cd /var/lib/frigate
    docker compose up -d
    docker compose logs -f

    Watch the logs until cameras connect and detection starts. The version is shown at the top of the System Metrics page once the UI is up.

    Step 7: Reverse proxy with automatic TLS

    Caddy gives you HTTPS with automatic Let's Encrypt certificates and a tiny config. Install it:

    shell
    sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https curl
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    sudo apt update
    sudo apt -y install caddy

    Replace /etc/caddy/Caddyfile with:

    shell
    frigate.example.com {
        reverse_proxy 127.0.0.1:8971
    }

    Reload Caddy:

    shell
    sudo systemctl reload caddy

    Visit https://frigate.example.com. Frigate 0.17 presents its own login page on port 8971. On first launch it generates an admin password and prints it in the logs:

    shell
    docker compose logs frigate | grep -i password

    Log in and change the password under the user settings.

    Step 8: Backups

    Two things matter for recovery: the config and the SQLite database. The recordings themselves are large and usually treated as expendable, but back them up too if your retention policy requires it.

    Create /usr/local/bin/frigate-backup.sh:

    shell
    #!/usr/bin/env bash
    set -euo pipefail
    STAMP=$(date +%Y%m%d-%H%M%S)
    DEST="/var/backups/frigate"
    mkdir -p "$DEST"
    # Config and SQLite database live under /config
    tar czf "$DEST/frigate-config-$STAMP.tar.gz" -C /var/lib/frigate config
    # Keep the last 14 archives
    ls -1t "$DEST"/frigate-config-*.tar.gz | tail -n +15 | xargs -r rm

    Make it executable and schedule it nightly:

    shell
    sudo chmod +x /usr/local/bin/frigate-backup.sh
    echo "30 3 * * * deploy /usr/local/bin/frigate-backup.sh" | sudo tee /etc/cron.d/frigate-backup

    Copy the archives off the VPS regularly (rsync over SSH, or push to object storage). A backup that lives only on the same disk is not a backup.

    Step 9: Updating

    The stable tag always points to the latest stable release after a pull.

    shell
    cd /var/lib/frigate
    docker compose pull
    docker compose up -d

    To pin a specific version instead, set the image tag explicitly, for example ghcr.io/blakeblackshear/frigate:0.17.0, and run docker compose up -d. Take a config backup before any upgrade so you can roll back by pinning the previous tag.

    Step 10: A note on notifications

    RamNode restricts outbound SMTP (port 25) on VPS plans by default to prevent abuse, and unblocking requires a support request. Do not rely on email notifications. Frigate supports MQTT and webhook based notifications and integrates cleanly with Home Assistant, which is a better fit for alerting anyway. If you want push notifications, configure them through the Frigate web app (PWA) or Home Assistant rather than email.

    Troubleshooting

    • Container exits with "Bus error": shm is too small. Recompute with the formula above, raise shm_size, and recreate the container.
    • Cameras will not connect: confirm the VPS can reach the camera over the tunnel first with ffprobe rtsp://... from inside the container (docker exec -it frigate ffprobe <url>). If that fails, it is a network or credentials problem, not a Frigate problem.
    • CPU pinned at 100 percent: you are detecting on a high resolution stream or at a high fps. Switch detection to a sub stream and lower fps to 5.
    • UI not loading through Caddy: confirm port 8971 is published and bound to localhost, and that the DNS A record resolves to the VPS before Caddy can issue a certificate.