Build a Fully Local, Privacy-First RTSP IP Camera with any Raspberry Pi
2026-07-29 | By Nate_Larson
License: Attribution 3D Printing Camera Wifi Wireless Raspberry Pi SBC
Introduction
If you already have Home Assistant and Frigate NVR set up, you're closer than you think to creating a fully local, open-source, wide-angle night-vision camera. This setup can be achieved for a fraction of the cost of a commercial IP camera, and it ensures that no video frames are sent to someone else's cloud. With this system, there are no subscriptions, no cloud accounts, no vendor lock-in, and no need to worry about what happens to your footage. Everything stays on your own hardware and within your own network.
Subscription-based "smart" cameras are widespread today, but their convenience comes with hidden costs beyond just the monthly fee. When your camera relies on a cloud backend, your video is leaving your property. This project serves as a direct alternative to that model, seamlessly integrating with the privacy-first, locally controlled framework that Home Assistant and Frigate are designed around.
The project uses a Raspberry Pi Zero W and a Pi NoIR Camera Module 3 Wide in a weatherproof enclosure. MediaMTX handles streaming H.264 video from the camera module as a standard RTSP stream using its native rpiCamera source; talking directly to the rpicamera stack without spawning external processes or piping stdout. Frigate picks up the stream just like any other IP camera. From Frigate's perspective, this is completely indistinguishable from a commercial camera: live view, recording, motion detection, and full Home Assistant integration all work out of the box.
For context, I built this to keep an eye on my chickens when they are in the run and to keep watch on the coop door. The same setup works just as well for a garage, workshop, front door, greenhouse, or anywhere you need a locally controlled camera with solid low-light performance, though for better night performance, you may want to add some IR LEDs for better visibility.
Prerequisites
This guide assumes you've already got Home Assistant and/or Frigate NVR up and running, though they are not required if you have another source you plan on piping your RTSP video stream into.
If you still need to get those set up, check out the following guides:
Keep an Eye on Your Stuff with Frigate NVR, an AI Enabled Video Recorder
Install Home Assistant and Set Up a Cost-Effective Smart Home Hub
Parts and Materials
· Raspberry Pi Zero W or any other Raspberry Pi SBC
· Raspberry Pi NoIR Camera Module 3 Wide or any other Raspberry Pi compatible camera
· Raspberry Pi Zero Camera Cable (150mm)
· Cable Gland (USB Power Entry)
· #2 Screws (Pi/Camera Mount)
· PETG Filament (3D Printed Mount)
You'll also need:
MicroSD card (8GB or larger)
USB power supply and weatherproof micro-USB cable
Why the NoIR Camera Module?
The NoIR (No Infrared) variant is what enables night vision functionality. Standard Pi camera modules come with an IR-cut filter that blocks infrared light, which is beneficial for accurate color representation during the day. However, this filter also obstructs the infrared spectrum that night-vision illumination relies on. By omitting this filter, the sensor can capture both visible and near-infrared light. In complete darkness, when an IR source is present, you can achieve a clear black-and-white image. Even without a dedicated IR illuminator, the NoIR sensor performs significantly better in low-light conditions compared to a standard camera module.
The Wide variant provides approximately a 120° field of view from a single mounting point, making it useful for monitoring a compact area without the need for multiple cameras or a pan/tilt arrangement.
Pi Zero W vs. Pi Zero 2 W — Which One?
For this project, I recommend the newer Raspberry Pi Zero 2 W, which features a quad-core Cortex-A53 processor and efficiently handles video encoding tasks. However, the original Raspberry Pi Zero W is also a viable option and is what I used since I had one lying around; any other Raspberry Pi with WiFi functionality will work, but you would need a different enclosure and camera cable.
MediaMTX talks directly to the VideoCore hardware encoder rather than burdening the CPU with software encoding. In testing with my original Raspberry Pi Zero W, MediaMTX with the native rpiCamera source used only around 11% CPU at steady state, leaving plenty of headroom.
Step 1: Flash Raspberry Pi OS Lite
Download the Raspberry Pi Imager and flash Raspberry Pi OS Lite to your microSD card. The Lite version is essential for working smoothly with the VideoCore hardware encoder on the Pi Zero family, making H.264 encoding easy, even on lighter hardware.
To learn how to flash an OS for Raspberry Pi, check out this guide: How to Flash an Operating System to SD Card for a Raspberry Pi
Before you write the image, click the gear icon in the imager to pre-configure a few things:
Hostname: something useful, like outdoorcam
Enable SSH: password or public key, your call
WiFi credentials: SSID and password for your network
Username: worth a moment's thought — the default is pi, but you can set whatever you like. Just remember it, because you'll need to put it in the systemd service file in Step 5.
Locale and timezone: set these for your region
Write the image, put the card in the Pi, power it up, and SSH in to make sure everything's working before you go any further:
ssh yourusername@outdoorcam.local
If you need assistance with this, I recommend this tutorial: How to Boot to Command Line and SSH on Raspberry Pi
This is also a great time to ensure everything is up-to-date:
sudo apt update && sudo apt upgrade -y
Then do a quick reboot before reconnecting over SSH to continue.
sudo reboot
Step 2: Verify rpicam-apps
The Raspberry Pi camera software suite was recently renamed to rpicam-apps. The old libcamera-* commands and their compatibility symlinks are gone in current Raspberry Pi OS releases, so libcamera-hello and similar commands no longer work. Recent versions of Raspberry Pi OS Lite ship with rpicam-apps pre-installed, but let's confirm:
rpicam-hello --version
If that spits out a version string, you're good. If it comes back as command not found:
sudo apt install -y rpicam-apps
Once that's resolved, verify the camera module is actually detected:
rpicam-hello --list-cameras
You should see the camera sensor listed with its available modes. If nothing shows up, the most likely culprit is the CSI ribbon cable since it's easy to not quite seat it fully, and the contacts need to face away from the PCB on both ends. The locking tab on the Pi Zero connector is particularly easy to miss.
How to Connect a Camera to a Raspberry Pi
Step 3: Configure GPU Memory
Note: This step is only necessary if you are using a Pi Zero board.
The Pi's VideoCore GPU handles H.264 encoding, but it needs enough memory headroom to do that reliably. Add this to /boot/firmware/config.txt:
echo 'gpu_mem=128' | sudo tee -a /boot/firmware/config.txt
That reserves 128MB for the GPU. Without it, the hardware encoder can fail or produce garbage output under sustained load. This takes effect following a reboot, which we'll do at the end once everything's configured.
Additionally, if you are using a Raspberry Pi Zero W or 2 W, I recommend you also review this guide: How to Keep a Raspberry Pi Zero W or Zero 2 W Reliably Connected to WiFi
Step 4: Install and Configure MediaMTX
MediaMTX (formerly rtsp-simple-server) is a lightweight and zero-dependency live media server and media proxy that allows publishing, reading, proxying, recording, and playback of real-time video and audio streams. Its native rpiCamera source talks directly to the camera stack, handling H.264 encoding via the VideoCore hardware encoder without spawning subprocesses or piping video over stdout. The result is a stable, low-overhead RTSP stream that Frigate connects to like any other IP camera and works directly in VLC as well.
MediaMTX's native rpiCamera source requires a couple of libcamera runtime libraries:
sudo apt install -y libcamera0.4 libfreetype6
Download the correct binary for the Pi Zero family's ARMv6 architecture. Check the MediaMTX releases page (https://github.com/bluenviron/mediamtx/releases) for the latest version and the correct architecture for your Raspberry Pi, and substitute accordingly:
wget https://github.com/bluenviron/mediamtx/releases/download/v1.19.0/mediamtx_v1.19.0_linux_armv6.tar.gz tar -xzf mediamtx_v1.19.0_linux_armv6.tar.gz sudo mkdir -p /opt/mediamtx sudo mv mediamtx mediamtx.yml /opt/mediamtx/
Now edit the configuration file:
sudo nano /opt/mediamtx/mediamtx.yml
The default config is heavily commented and quite long. Change the "rtspTransports: [udp, multicast, tcp]" line near the top of the file with the other top-level settings to:
rtspTransports: [tcp]
Also, find the "paths:" section near the bottom and replace everything under it with the following:
paths:
cam:
source: rpiCamera
sourceOnDemand: no
rpiCameraWidth: 1280
rpiCameraHeight: 720
rpiCameraFPS: 7
rpiCameraBitrate: 1000000
rpiCameraCodec: hardwareH264
rpiCameraIDRPeriod: 10
rpiCameraMode: 2304:1296:10:PA few things worth understanding here:
rtspTransports: [tcp]- forces RTSP over TCP rather than UDP. On WiFi, UDP packet loss causes H.264 decode errors in Frigate. TCP eliminates that.rpiCameraBitrate: 1000000- sets a conservative 1Mbps bitrate. The default of 5Mbps causes instability on the Pi Zero family — don't skip this if you are using a Pi Zero W or Zero 2 W.rpiCameraMode: 2304:1296:10:P- tells the sensor to read a large portion of the IMX708 sensor area and downscale to the output resolution. Without this, MediaMTX defaults to reading only the center 1536x864 pixels, producing a significantly cropped image. That defeats the entire purpose of using the Wide module, so this setting is not optional if you are using a wide-angle camera.rpiCameraIDRPeriod: 10- controls how often the Raspberry Pi camera encoder inserts an IDR (Instantaneous Decoder Refresh) frame, also known as a keyframe, into an H.264 video stream. A lower value produces more frequent keyframes, improving stream recovery and reducing the time it takes new viewers to begin playback, while a higher value reduces bandwidth overhead at the cost of slower recovery from packet loss or when clients join the stream. The default value is 60, but I set my config to 10 because I was having issues with frame tearing and stream corruption.rpiCameraFPS: 7- sets the target frame rate, in frames per second (FPS), for the Raspberry Pi camera stream. Higher frame rates provide smoother motion but require more processing power and bandwidth, while lower frame rates reduce resource usage. The default is 30, but I lowered this due to the limited bandwidth of the Pi Zero W.sourceOnDemand- controls whether the camera runs continuously or only when a client is connected. In my testing with a Pi Zero W, I found that when clients disconnect from the video server and the stream stops, it can sometimes cause the MediaMTX service to hang, so this is an important tradeoff to understand:yes - the camera spins up when a client connects and shuts down when the last client disconnects. Lower power draw and less wear on the camera hardware, but if you are integrating the stream directly with Home Assistant and not using the Frigate restream, and Home Assistant tries to grab a snapshot via an automation while no one is actively watching the stream, it will get a black frame or nothing at all while the stream cold-starts. Fine for a camera if you're primarily watching it live.
no - the camera runs 24/7 regardless of whether anyone is watching. Snapshot automations in HA will always get a valid frame. The right choice for any camera where you need reliable automation-triggered snapshots, such as a 3D printer monitor, a front door camera, etc.
Set this based on your use case. The rest of this guide uses yes as the default, but change it to no if you need reliable snapshot support.
Camera Module Variants
The config above is written for the Camera Module 3 Wide (standard or NoIR, 120° FOV). If you're using a different module, here's what changes:
Camera Module 3 (standard or NoIR, 75° FOV) — IMX708
The standard Camera Module 3 uses the same IMX708 sensor and the same sensor modes as the Wide. The config is identical, except for the lens, which gives you 75° instead of 120° FOV. So no changes to the configuration should be needed.
Camera Module 2 (standard or NoIR) — IMX219
The Camera Module 2 uses Sony's IMX219 sensor with different native modes. Use "1640:1232" as the sensor mode, which reads the full sensor area and downscales to your output resolution, the same principle as the Wide module config above:
paths:
cam:
source: rpiCamera
sourceOnDemand: no
rpiCameraWidth: 1280
rpiCameraHeight: 720
rpiCameraFPS: 7
rpiCameraBitrate: 1000000
rpiCameraCodec: hardwareH264
rpiCameraIDRPeriod: 30
rpiCameraMode: 1640:1232:10:PThe IMX219 has a narrower native FOV than the IMX708 Wide (roughly 62° diagonal), so the cover area per camera is smaller. If you're using the NoIR variant, you may notice a color cast in daylight due to the missing IR-cut filter. This is normal and can be corrected by passing a tuning file to the camera stack, though for a pure monitoring application in low light, it generally doesn't matter.
You can find more information on the official Raspberry Pi camera modules here: https://www.raspberrypi.com/documentation/accessories/camera.html#about-the-camera-modules
As always, run "rpicam-hello --list-cameras" on your Pi first to confirm what sensor modes your specific module reports. The output tells you exactly what to put in rpiCameraMode.
Step 5: Create a systemd Service
Setting MediaMTX up as a systemd service means it starts automatically on boot and restarts itself if it ever crashes, which is exactly what you want for an unattended camera.
sudo nano /etc/systemd/system/mediamtx.service
Paste in:
[Unit] Description=MediaMTX Wants=network-online.target After=network-online.target [Service] User=yourusername ExecStart=/opt/mediamtx/mediamtx /opt/mediamtx/mediamtx.yml Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
Replace "yourusername" with the username you set in Step 1. Enable and start it:
sudo systemctl daemon-reload sudo systemctl enable mediamtx sudo systemctl start mediamtx sudo systemctl status mediamtx
Step 6: Enable the Hardware Watchdog
A hardware watchdog is worth setting up for any Pi that's going to live somewhere you can't easily reach. Raspberry Pi SBCs have a built-in watchdog timer that'll force a hard reboot if the OS stops checking in due to kernel panics, complete hangs, or whatever. It's a safety net that pure software solutions can't provide.
Enable the watchdog overlay and kernel module:
echo 'dtparam=watchdog=on' | sudo tee -a /boot/firmware/config.txt echo 'bcm2835_wdt' | sudo tee -a /etc/modules
Then configure systemd to actually feed the watchdog:
sudo nano /etc/systemd/system.conf
Find and uncomment (or add) these two lines:
RuntimeWatchdogSec=15 RebootWatchdogSec=2min
With RuntimeWatchdogSec=15, the hardware watchdog triggers a forced reboot if the system goes 15 seconds without checking in. Long enough to ride out brief load spikes, but short enough to recover quickly from a real hang.
Step 7: Reboot and Verify
Apply everything with a reboot:
sudo reboot
Once it's back up, verify if the mediamtx service is running:
sudo systemctl status mediamtx
You should see MediaMTX running, with the rpiCamera component active. You can also open the network stream directly in VLC on any machine on your network:
rtsp://[Pi IP address]:8554/cam
If you get a clean stream in VLC, you're ready to add the new camera stream to Frigate.
Step 8: Add the Camera to Frigate
On your Frigate host, add this to config.yml under cameras:
cameras:
outdoor_camera:
ffmpeg:
inputs:
- path: rtsp://[Pi IP address]:8554/cam
roles:
- detect
- record
detect:
width: 1280
height: 720
fps: 7
Restart Frigate to pick up the change. If you have Frigate tied into your Home Assistant server, the camera will also appear as a new entity in Home Assistant through the existing Frigate integration and can be easily added to any dashboard as a camera card. However, you will have lower latency in Home Assistant if you add the camera's direct stream to your server rather than using the restream provided by Frigate.
Step 9: Add the Camera Directly to Home Assistant
If you're not using Frigate, or want the stream available in HA independently of Frigate, you can add the MediaMTX RTSP stream directly to Home Assistant using the built-in Generic Camera integration. This gives you a live view card and snapshot capability without the additional latency of an NVR in the middle.
In Home Assistant, go to Settings → Devices & Services → Add Integration and search for Generic Camera. When prompted, enter:
Stream source URL: rtsp://[Pi IP address]:8554/cam
Still image URL: leave blank — HA will pull stills directly from the stream
Username / Password: leave blank
Verify SSL: off
Give the camera a name and save. It shows up as a camera entity in HA and can be added to any dashboard as a camera card, used in automations for snapshots, or referenced in notifications.
Note that if you have "sourceOnDemand: yes" in your MediaMTX config, snapshot automations may return a black frame when the stream isn't already active. See the note in Step 4 about this tradeoff.
Step 10: Physical Assembly
3D Printed Mount
I designed and printed a custom mount using PETG to securely hold the Raspberry Pi Zero W and the camera module inside the enclosure I selected for my camera. This setup ensures that the camera lens is correctly aligned with the dome window. PETG is an ideal material for this project because it performs better than PLA in handling the temperature fluctuations typical of an outdoor enclosure. The Raspberry Pi and camera board are attached to the mount using #2 screws. There is also a camera cover to mitigate reflections of the camera or the Pi's power LED on the camera dome.
Cable Entry
Power comes in through a cable gland, which makes a weatherproof seal around the USB cable. Drill a hole sized to your gland's thread, feed the cable through, tighten the gland from outside, and connect to the Pi's micro-USB power input inside. The enclosure has a built-in gasket at the dome window and enclosure cover, so nothing extra is needed there.
Mounting
Mount the enclosure where the 120° wide-angle lens gets a clean view of the area you want to cover. Secure it with fasteners appropriate for your application.
Conclusion
Once it is up and running, the camera appears in Home Assistant as a fully integrated Frigate entity, featuring live view, recording, motion detection, and event clips, all operating entirely on your own hardware without any external dependencies. The Pi Zero W pulls around 300–400mA at 5V under load, so it's efficient enough to run continuously without any concerns. In testing, MediaMTX used around 11% CPU at steady state on the Pi Zero W, with a 46°C operating temperature and comfortable memory headroom, which is well within the sustained operating envelope of this hardware.
The setup scales well, too. Anywhere you have power and WiFi, you can spin up another camera with the same process: add a stream in Frigate, get a new entity in Home Assistant, done. No new subscriptions, no new accounts, no new cloud services.
I've used this configuration to add several new cameras to my Home Assistant server and Frigate, and they have all been running autonomously for several weeks without any issues once I stabilized my Pi Zero W' board's WiFi connection following the instructions here: How to Keep a Raspberry Pi Zero W or Zero 2 W Reliably Connected to WiFi

