Setting up Frigate NVR on a dedicated Debian server (Intel N97) to replace a traditional NVR. Covers Docker compose, go2rtc stream config, hardware acceleration, HA integration, push notifications, and zone-based alerting. Traffic between VLANs goes through the main router (UCG Ultra).
Hardware & Context#
- Server: Intel N97 mini PC (
debian.lan,10.0.10.11), Debian 13 - Cameras: 8 Reolink PoE cameras on camera VLAN (
10.0.40.0/24), 2 Nanit monitors on IoT VLAN (10.0.20.0/24) - Existing NVR: kept running for continuous recording; Frigate handles detection and event clips only
- Home Assistant: on IoT VLAN (
10.0.20.10), MQTT broker already running
Storage Design#
Frigate recordings go to a dedicated NAS share — no local NVMe waste for surveillance video.
On Synology DSM: create shared folder surveillance, NFS export to
10.0.10.11 with Map all users to admin squash. This avoids UID mismatch
issues since the Frigate container UID (1001) has no corresponding NAS user.
Mount on debian:
| |
Add to /etc/fstab:
| |
Docker Compose#
| |
| |
Credentials are referenced in config.yml as {FRIGATE_VARIABLE_NAME} —
Frigate resolves these from container environment variables, keeping passwords
out of the config file itself.
shm_size: 256mb — shared memory for frame buffers. 8 cameras at 5fps
detection streams needs roughly 20–30 MB; 256 MB is a safe headroom.
/dev/dri/renderD128 — Intel iGPU for hardware-accelerated video decoding
(VAAPI). No privileged: true needed; passing the device directly is sufficient.
config.yml#
MQTT & Hardware#
| |
ffmpeg vs detector acceleration: preset-vaapi handles video decoding (H.264/H.265 frames → raw pixels) using the Intel iGPU’s media engine. The OpenVINO detector handles inference (running the object detection model) using the iGPU’s execution units. Both run on the same Intel N97 iGPU but use different hardware blocks, so they coexist without contention.
Why OpenVINO over CPU? The N97 supports OpenVINO natively. OpenVINO inference on GPU is 3–5× faster than CPU, at no hardware cost. The default model (ssdlite_mobilenet_v2) is the same — only the execution backend changes.
Model path must be at the top level. The model: block is global config, not nested under detectors. Putting path inside detectors.ov.model silently results in a None path and a startup crash.
MQTT host is HA’s IP (10.0.20.10). Traffic routes through the UCG Ultra.
Recording#
| |
No retain.days at the top level means no continuous recording — only
event clips are saved. The existing NVR handles 24/7 recording; Frigate stores
short tagged clips around detection events. These are complementary, not
duplicates.
go2rtc Streams — Pitfall#
Frigate uses go2rtc internally for stream management. Each camera needs two named streams: one for recording (main, high-res) and one for detection (sub-stream, low-res). The key mistake to avoid:
Wrong — both streams bundled under one name:
| |
go2rtc treats multiple sources as fallbacks — it picks one. Referencing
front_sub elsewhere returns 404.
Correct — separate named streams:
| |
Then cameras reference them:
| |
Reolink RTSP URLs#
Standard Reolink cameras:
- Main:
rtsp://admin:pass@IP:554/h264Preview_01_main - Sub:
rtsp://admin:pass@IP:554/h264Preview_01_sub
Newer models (CX810) use H.265:
rtsp://admin:pass@IP:554/h265Preview_01_main
Reolink Duo has two lenses — Preview_01 and Preview_02. In practice only
one stream is usefully exposed; treat it as a single camera.
Camera Config#
| |
detect.enabled: true is required. In Frigate 0.17 it defaults to
false. When streams fail at startup (e.g. before go2rtc connects), Frigate
auto-disables detection and does not re-enable it — even after streams recover.
Always set it explicitly.
Match width/height to the actual sub-stream resolution. Frigate
resizes frames to this resolution before running detection. A mismatch causes
stretching which distorts object shapes and hurts detection accuracy —
especially for distant or small subjects. Use ffprobe on the sub-stream to
check:
| |
My camera sub-stream resolutions:
| Camera | Sub-stream | Notes |
|---|---|---|
| Reolink standard (front, backyard, side_a/b, cx810) | 640×360 | 16:9 |
| Reolink E1 | 640×360 | PTZ — no zones |
| Reolink doorbell | 480×640 | Portrait orientation — width/height swapped |
| Reolink Duo (duo_a) | 1536×576 | Ultra-wide 8:3; detect at 1280×480 |
Detection Tuning#
Default thresholds: min_score: 0.5, threshold: 0.7. The threshold is the
running average confidence across the tracking window — intermittent
low-confidence detections may not reach it.
For wide-angle or distant cameras, lower thresholds and higher detect resolution help:
| |
Zones & Alerting#
Frigate 0.17 splits events into alerts (high-priority) and detections
(low-priority). By default, all person and car detections anywhere in
frame are alerts. Zones let you restrict this.
How it works#
- Zone polygons are drawn in the Frigate UI (visual editor) and saved to
config.ymlautomatically - A zone’s
objectslist restricts which labels activate it loitering_timerequires an object to remain in the zone for N seconds before the zone is considered “active”required_zonesunderreview.alertsgates which events become alerts
The correct placement for required_zones:
| |
required_zones goes under cameras.<name>.review.alerts — not under
objects.filters (that key doesn’t exist and causes a config error).
Result#
| Event | Outcome |
|---|---|
| Car passing through | Detection only, no alert |
| Car parked in zone 3+ min | Alert |
| Person walking through | Detection only, no alert |
| Person lingering in zone 30+ sec | Alert |
Home Assistant Integration#
Frigate Integration#
Install via HA: Settings → Integrations → Add → Frigate
Point it at Frigate’s main LAN IP: http://10.0.10.11:5000
Push Notifications#
HA automation that fires on new person detection with a snapshot:
| |
Why type == 'new' not type == 'end'? Triggering on end gives a
complete clip but delays the notification — and never fires if the person
stays in frame indefinitely. new fires immediately; the 2-second delay
gives Frigate time to generate the first snapshot.
The snapshot URL uses Frigate’s LAN IP (10.0.10.11); HA fetches it via the
main router. The tap URL uses the Tailscale hostname for remote access.
Lessons#
go2rtc stream names must be unique per stream quality. Bundling main+sub under one name makes the sub stream unreachable via go2rtc’s RTSP server.
detect.enableddefaults to false in Frigate 0.17. Set it explicitly in every camera config. If streams fail at startup, Frigate disables detection and doesn’t recover automatically.required_zonesbelongs underreview.alerts, notobjects.filters. Putting it under filters causes a config validation error.RTSP must be explicitly enabled on some cameras. Newer Reolink models (CX810) have RTSP disabled by default in their web UI.
connection refusedon port 554 is the symptom.H.265 cameras need different RTSP paths. Use
h265Preview_01_maininstead ofh264Preview_01_mainfor H.265 cameras.Live view and detection are independent. The browser gets video via WebRTC directly from go2rtc. Even if detection is broken, live view works. A working live stream does not confirm detection is running.
HA↔Frigate connectivity goes through the main router. Make sure the UCG Ultra firewall allows IoT VLAN → main LAN traffic on port 5000 and 1935.