Argus NVR: One Open Port, Local Face Recognition, No Cloud Dependency

How Argus NVR proxies WebRTC camera traffic through a single port and keeps face recognition, credentials, and footage entirely on-box.

Most self-hosted NVR setups end up exposing more than they mean to. You add a camera, go2rtc opens its signaling and media ports, and now you’re deciding which of three services to firewall. Argus collapses that surface to one port.

go2rtc handles the RTSP restreaming, one connection per camera, fanned out internally to the recorder and the detector. But browsers never talk to go2rtc directly. FastAPI reverse-proxies it, HTTP and WebSocket both, over port 8080. go2rtc’s own ports, 1984, 8554, 8555, stay bound to 127.0.0.1. The only thing that needs exposing to your LAN is the app itself.

Recording is split into two paths that don’t compete for CPU. FFmpeg stream-copies straight to fragmented MP4, no re-encode, which means near-zero CPU load for continuous 24/7 capture and crash-safe segments if the box loses power mid-recording. Motion detection runs separately: frame-diff plus a scipy blob filter, with per-camera zones and sensitivity. Hikvision cameras skip frame-diff entirely and subscribe to the ISAPI alert stream for hardware VMD events instead.

Face recognition runs on InsightFace’s buffalo_sc model, a 512-dimension ONNX model, CPU only. No GPU requirement, no cloud inference call. The embeddings themselves are stored as raw numpy float32 bytes rather than pickled objects, which closes off the deserialization RCE class of bug you get when a face-recognition pipeline unpickles stored vectors it doesn’t fully trust.

Auth is JWT Bearer on every /api/* route except the health check. The one deliberate exception is media: /recordings/*/video, thumbnails, heatmaps, and snapshots also accept a ?token= query param, because <video> and <img> tags can’t attach custom headers. Camera passwords are Fernet-encrypted at rest using the same secret key that signs the JWTs. RBAC gates the operations that matter: admin, operator, viewer, with user management, audit log access, and recording purges locked to admin. Every login, password reset, and camera change lands in the audit log with username, IP, and timestamp.

Webhooks get HMAC-SHA256 signatures in an X-Argus-Signature header, so a receiver can verify a notification actually came from your NVR before acting on it. And the README is blunt about the one thing it can’t fix for you: don’t put port 8080 on the public internet. Use Tailscale, or put TLS in front of it yourself.

Full paper →

↑