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.
| Component | Purpose | Estimated Cost |
|---|---|---|
| PoE cameras | Capture video | $25-50 each |
| PoE switch | Power and connect cameras | $50-80 |
| Mini PC or server | Run 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
| Factor | PoE (Wired) | WiFi |
|---|---|---|
| Reliability | Rock solid | Drops during congestion |
| Installation | One cable per camera | No cables, but needs power |
| Bandwidth | Full 100 Mbps per camera | Shared across all WiFi devices |
| Power | Cable-powered (PoE) | Battery or wall adapter |
| Cost | Higher (cable runs + switch) | Lower initial |
| Recommendation | Use this | Acceptable 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.
Recommended Cameras
| Use Case | Camera | Resolution | Price | RTSP? |
|---|---|---|---|---|
| Outdoor (budget) | Reolink RLC-510A | 5 MP | $28-35 | Yes |
| Outdoor (mid-range) | Reolink RLC-811A | 4K | $50-65 | Yes |
| Indoor | Reolink E1 Zoom | 5 MP | $40-50 | Yes (PTZ) |
| Doorbell | Amcrest AD410 | 2K | $80-100 | Yes |
| High-end outdoor | Dahua IPC-HDW3849H | 4K | $80-120 | Yes |
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.
Recommended Setup (VLAN Isolation)
Many IP cameras phone home to manufacturer cloud services. Isolate cameras on a VLAN with no internet access:
- Create a VLAN (e.g., VLAN 20, subnet 10.20.0.0/24) on your router
- Tag camera ports on your managed switch as VLAN 20
- Add a firewall rule: VLAN 20 → block all internet traffic
- Add a firewall rule: NVR server → allow traffic to VLAN 20 (for pulling RTSP streams)
- 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
| Brand | Main Stream | Sub Stream |
|---|---|---|
| Reolink | rtsp://[ip]:554/h264Preview_01_main | rtsp://[ip]:554/h264Preview_01_sub |
| Dahua | rtsp://[user]:[pass]@[ip]:554/cam/realmonitor?channel=1&subtype=0 | subtype=1 |
| Hikvision | rtsp://[user]:[pass]@[ip]:554/Streaming/Channels/101 | /Channels/102 |
| Amcrest | rtsp://[user]:[pass]@[ip]:554/cam/realmonitor?channel=1&subtype=0 | subtype=1 |
| Generic ONVIF | Discover 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:[email protected]: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
| NVR | Best For | Detection | Complexity |
|---|---|---|---|
| Frigate | Home users with Home Assistant | Coral TPU / GPU / CPU | Medium |
| ZoneMinder | Large deployments (20+ cameras) | Plugin-based (YOLO) | High |
| Shinobi | Web-based monitoring | Plugin-based | Medium |
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:[email protected]:554/h264Preview_01_main
roles:
- record
- path: rtsp://admin:[email protected]: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:[email protected]:554/h264Preview_01_main
roles:
- record
- path: rtsp://admin:[email protected]: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:
- Open a camera’s live view
- Click “Debug” → “Zone editor”
- Draw polygons around areas you want to monitor (doorway, driveway, walkway)
- 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
Option A: Home Assistant Integration (Recommended)
- Install the Frigate integration in Home Assistant via HACS
- Enable MQTT in Frigate’s
config.yml(point to your MQTT broker — Mosquitto recommended) - 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:
| Method | Complexity | Security | Latency |
|---|---|---|---|
| Tailscale | Easy | Excellent (WireGuard) | Low |
| Cloudflare Tunnel | Medium | Good | Medium |
| WireGuard | Medium | Excellent | Low |
| Port forwarding | Easy | Poor (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
Related
- Best Self-Hosted Video Surveillance
- NVR Hardware Guide
- PoE Camera Systems
- How to Self-Host Frigate
- How to Self-Host ZoneMinder
- How to Self-Host Shinobi
- Frigate vs ZoneMinder
- Self-Hosted Alternatives to Ring
- Self-Hosted Alternatives to Nest Cam
- Docker Compose Basics
- Reverse Proxy Setup
- Backup Strategy
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments