Self-Hosted NVR Setup Guide

What You’ll Build

By the end of this guide, you’ll have a working self-hosted surveillance system: cameras streaming to an NVR on your network, with AI-powered object detection that sends notifications to your phone. No cloud subscriptions, no third-party access to your footage.

Updated February 2026: Verified with latest Docker images and configurations.

ComponentPurposeEstimated Cost
PoE camerasCapture video$25-50 each
PoE switchPower and connect cameras$50-80
Mini PC or serverRun NVR software$100-200
AI accelerator (Coral TPU)Object detection$25-60
Storage (HDD)Record footage$55-100
Total (4 cameras)$330-590

This replaces $150-250/year in cloud NVR subscriptions (Ring, Nest, Arlo). It pays for itself within 2 years.

Prerequisites

  • Basic familiarity with Docker and Docker Compose (Docker Compose Basics)
  • A Linux server or mini PC (see our NVR hardware guide)
  • A router that supports port forwarding or VLANs (recommended, not required)
  • A network switch — preferably PoE (Power over Ethernet)

Step 1: Choose Your Cameras

Camera Protocol: RTSP

Your cameras must support RTSP (Real Time Streaming Protocol). This is the standard protocol that NVR software uses to pull video from cameras. Most IP cameras support RTSP. Ring, Nest, and Arlo cameras do not — they use proprietary protocols locked to their cloud services.

Wired vs. WiFi

FactorPoE (Wired)WiFi
ReliabilityRock solidDrops during congestion
InstallationOne cable per cameraNo cables, but needs power
BandwidthFull 100 Mbps per cameraShared across all WiFi devices
PowerCable-powered (PoE)Battery or wall adapter
CostHigher (cable runs + switch)Lower initial
RecommendationUse thisAcceptable for 1-2 cameras indoors

Run PoE if you can. One Cat6 cable delivers power and data. No batteries to recharge, no WiFi dead zones, no bandwidth contention with your other devices.

Use CaseCameraResolutionPriceRTSP?
Outdoor (budget)Reolink RLC-510A5 MP$28-35Yes
Outdoor (mid-range)Reolink RLC-811A4K$50-65Yes
IndoorReolink E1 Zoom5 MP$40-50Yes (PTZ)
DoorbellAmcrest AD4102K$80-100Yes
High-end outdoorDahua IPC-HDW3849H4K$80-120Yes

Avoid Wyze cameras unless you flash RTSP firmware — their stock firmware routes through Wyze’s cloud.

Step 2: Set Up Your Network

Basic Setup (No VLAN)

Connect your PoE switch to your router. Plug cameras into the PoE switch. The cameras will get IP addresses from your router’s DHCP.

[Internet] → [Router] → [PoE Switch] → [Camera 1]
                │                     → [Camera 2]
                │                     → [Camera 3]
                └── [NVR Server]

Assign static IPs to cameras. Cameras changing IP addresses will break your NVR config. Most cameras let you set a static IP through their web interface (default port 80 or 443). Check your camera’s manual for default credentials.

Many IP cameras phone home to manufacturer cloud services. Isolate cameras on a VLAN with no internet access:

  1. Create a VLAN (e.g., VLAN 20, subnet 10.20.0.0/24) on your router
  2. Tag camera ports on your managed switch as VLAN 20
  3. Add a firewall rule: VLAN 20 → block all internet traffic
  4. Add a firewall rule: NVR server → allow traffic to VLAN 20 (for pulling RTSP streams)
  5. Cameras can stream to the NVR but cannot reach the internet

This requires a managed switch (not an unmanaged PoE switch) and a router that supports VLANs (OPNsense, pfSense, UniFi, MikroTik).

Step 3: Find Your Camera RTSP URLs

Every camera has an RTSP URL for its video stream. You’ll need this URL to configure your NVR.

Common RTSP URL Patterns

BrandMain StreamSub Stream
Reolinkrtsp://[ip]:554/h264Preview_01_mainrtsp://[ip]:554/h264Preview_01_sub
Dahuartsp://[user]:[pass]@[ip]:554/cam/realmonitor?channel=1&subtype=0subtype=1
Hikvisionrtsp://[user]:[pass]@[ip]:554/Streaming/Channels/101/Channels/102
Amcrestrtsp://[user]:[pass]@[ip]:554/cam/realmonitor?channel=1&subtype=0subtype=1
Generic ONVIFDiscover with ffprobe or ONVIF Device Manager

Main stream vs. sub stream: The main stream is full resolution (for recording). The sub stream is lower resolution (for live viewing and object detection). Configure your NVR to use both — record the main stream, detect on the sub stream.

Test Your RTSP Stream

Before configuring your NVR, verify the stream works:

# Install ffprobe if not present
sudo apt install ffmpeg

# Test stream connectivity
ffprobe -rtsp_transport tcp rtsp://admin:password@192.168.1.100:554/h264Preview_01_main

If you see codec/resolution information, the stream is working. If it times out, check credentials, IP, and port.

Step 4: Install NVR Software

Choosing Your NVR

NVRBest ForDetectionComplexity
FrigateHome users with Home AssistantCoral TPU / GPU / CPUMedium
ZoneMinderLarge deployments (20+ cameras)Plugin-based (YOLO)High
ShinobiWeb-based monitoringPlugin-basedMedium

For most people: use Frigate. It has the best AI detection, the largest community, and native Home Assistant integration. This guide uses Frigate for the remaining steps.

Install Frigate with Docker Compose

Create a directory for your Frigate config:

mkdir -p /opt/frigate/config
mkdir -p /opt/frigate/storage

Create /opt/frigate/docker-compose.yml:

services:
  frigate:
    container_name: frigate
    image: ghcr.io/blakeblackshear/frigate:0.17.0
    restart: unless-stopped
    privileged: true
    shm_size: "256mb"
    volumes:
      - ./config:/config
      - ./storage:/media/frigate
      - /etc/localtime:/etc/localtime:ro
      # Coral USB — uncomment if using USB accelerator
      # - /dev/bus/usb:/dev/bus/usb
    ports:
      - "5000:5000"   # Web UI
      - "8554:8554"   # RTSP restream
      - "8555:8555/tcp" # WebRTC over TCP
      - "8555:8555/udp" # WebRTC over UDP
    environment:
      FRIGATE_RTSP_PASSWORD: "changeme"
    devices:
      # Google Coral USB — uncomment if using
      # - /dev/bus/usb:/dev/bus/usb
      # Coral M.2 — uncomment if using
      # - /dev/apex_0:/dev/apex_0

Create /opt/frigate/config/config.yml:

mqtt:
  enabled: false
  # Enable if using Home Assistant:
  # host: 192.168.1.50
  # port: 1883

detectors:
  coral:
    type: edgetpu
    device: usb
    # For M.2 Coral, use:
    # device: pci

cameras:
  front_door:
    ffmpeg:
      inputs:
        - path: rtsp://admin:password@192.168.1.100:554/h264Preview_01_main
          roles:
            - record
        - path: rtsp://admin:password@192.168.1.100:554/h264Preview_01_sub
          roles:
            - detect
    detect:
      width: 640
      height: 480
      fps: 5
    objects:
      track:
        - person
        - car
        - dog
        - cat
    record:
      enabled: true
      retain:
        days: 14
        mode: motion
      events:
        retain:
          default: 30
          mode: active_objects
    snapshots:
      enabled: true
      retain:
        default: 30

  backyard:
    ffmpeg:
      inputs:
        - path: rtsp://admin:password@192.168.1.101:554/h264Preview_01_main
          roles:
            - record
        - path: rtsp://admin:password@192.168.1.101:554/h264Preview_01_sub
          roles:
            - detect
    detect:
      width: 640
      height: 480
      fps: 5
    objects:
      track:
        - person
        - car
        - dog
    record:
      enabled: true
      retain:
        days: 14
        mode: motion
      events:
        retain:
          default: 30
          mode: active_objects

Start Frigate:

cd /opt/frigate && docker compose up -d

Access the web UI at http://your-server-ip:5000.

Step 5: Configure Object Detection

Detection Zones

Reduce false positives by defining detection zones. In Frigate’s web UI:

  1. Open a camera’s live view
  2. Click “Debug” → “Zone editor”
  3. Draw polygons around areas you want to monitor (doorway, driveway, walkway)
  4. Add the zone coordinates to config.yml:
cameras:
  front_door:
    zones:
      doorway:
        coordinates: 0.2,0.3,0.8,0.3,0.8,0.9,0.2,0.9
        objects:
          - person
      driveway:
        coordinates: 0.0,0.5,0.4,0.5,0.4,1.0,0.0,1.0
        objects:
          - car
          - person

Detection FPS

Set detection FPS to 5 for most cameras. Higher FPS burns more Coral cycles without meaningful improvement in detection accuracy. Only increase for fast-moving scenes (roadside cameras).

Object Filters

Reduce false detections with minimum score thresholds:

objects:
  track:
    - person
    - car
  filters:
    person:
      min_score: 0.6
      threshold: 0.7
    car:
      min_score: 0.6
      threshold: 0.7

Step 6: Set Up Notifications

  1. Install the Frigate integration in Home Assistant via HACS
  2. Enable MQTT in Frigate’s config.yml (point to your MQTT broker — Mosquitto recommended)
  3. Create an automation in Home Assistant:
# Home Assistant automation example
automation:
  - alias: "Frigate - Person at Front Door"
    trigger:
      - platform: mqtt
        topic: frigate/events
        value_template: "{{ value_json['after']['camera'] }}"
        payload: "front_door"
    condition:
      - condition: template
        value_template: "{{ trigger.payload_json['after']['label'] == 'person' }}"
      - condition: template
        value_template: "{{ trigger.payload_json['type'] == 'new' }}"
    action:
      - service: notify.mobile_app_your_phone
        data:
          title: "Person Detected"
          message: "Person at front door"
          data:
            image: "https://your-ha-url/api/frigate/notifications/{{ trigger.payload_json['after']['id'] }}/thumbnail.jpg"

Option B: Ntfy (No Home Assistant)

If you don’t run Home Assistant, use Ntfy for push notifications:

# Script: /opt/frigate/notify.sh
#!/bin/bash
# Triggered by Frigate via webhook
EVENT_ID=$1
curl -X POST "https://ntfy.sh/your-topic" \
  -H "Title: Motion Detected" \
  -H "Priority: high" \
  -d "Person detected on camera"

Step 7: Remote Access

Access your NVR from outside your home network:

MethodComplexitySecurityLatency
TailscaleEasyExcellent (WireGuard)Low
Cloudflare TunnelMediumGoodMedium
WireGuardMediumExcellentLow
Port forwardingEasyPoor (exposed to internet)Low

Use Tailscale. Install on your server and your phone. Access Frigate’s web UI at http://100.x.x.x:5000 from anywhere. No ports opened, no DNS configuration, end-to-end encrypted. Free for personal use.

Common Mistakes

1. Using WiFi Cameras for Outdoor Surveillance

WiFi cameras drop frames during rain (water absorbs 2.4 GHz signals), compete for bandwidth with every other device, and need batteries or power adapters. PoE cameras connected by cable eliminate all of these issues.

2. Skipping the Sub Stream

Sending the main 4K stream to both recording and detection wastes resources. The detector only needs 640×480 or 320×240. Always configure separate main (record) and sub (detect) streams.

3. Using :latest Docker Tags

Pin your Frigate image version. A breaking config change in a new release can take your surveillance offline at the worst time. Update deliberately, after reading release notes.

4. No Backup for Recordings

Your NVR’s hard drive will eventually fail. If 30 days of footage matters to you, set up automated backup to a second drive or NAS. See our backup strategy guide.

5. Cameras With Internet Access

Unless the camera needs cloud features you actually use, block its internet access. Many cameras send telemetry to manufacturer servers. Use a VLAN or firewall rules.

Next Steps

  • Add more cameras — edit config.yml, add a new camera block, restart Frigate
  • Set up face recognition — add Double Take + CompreFace for familiar face detection
  • Integrate with Home Assistant — automate lights, locks, and alarms based on detection events
  • Monitor your NVR — use Uptime Kuma to alert if Frigate goes down

Comments