I wanted a home surveillance camera I could actually trust — no cloud subscriptions, no third-party servers, nothing phoning home. Just a Raspberry Pi, a camera module, and a web interface I built myself with Claude. The result is a live MJPEG stream, H.264 video recording, a privacy mode, and a scheduling system, all behind a login page. Zero external dependencies beyond the camera library itself.
// what it is
A password-protected web interface for a Raspberry Pi Camera. You open a browser, log in, and you get a live stream. From there you can start and stop recordings, toggle privacy mode to cut the feed, and configure a schedule so the camera only runs during the hours you want — say, overnight while you're asleep, or while you're at work.
The whole thing is a single Python file (camera_web_secure.py) running
on Python's built-in http.server. No Flask, no Django, no web framework
at all. It runs as a systemd service under a dedicated picam
user, so it starts automatically on boot and restarts itself if it crashes.
// how it works
The camera side uses picamera2 — the modern Python library for Raspberry Pi cameras. It runs two encoders simultaneously: a JPEG encoder for the live stream and an H.264 encoder for recordings. The live stream is served as MJPEG over a plain HTTP connection, which any browser can display natively without plugins.
Authentication is handled with a simple session system. Passwords are hashed with
SHA-256 and stored in a local config file. When you log in, the server creates a
session token using secrets.token_urlsafe(), drops it in a
HttpOnly cookie, and remembers it for one hour. Every protected endpoint
checks for a valid, non-expired session before responding.
The schedule system is the part I'm most happy with. You configure active days and a time window (which can span midnight — e.g. 20:00 to 08:00), and a background thread checks every minute whether the camera should be running. If you're outside the active window, the camera pauses itself. When the window opens again, it resumes. A manual privacy toggle overrides the schedule entirely until you turn it off.
Finally, there's an emergency cleanup script (cleanup_camera.sh) for
when the camera gets stuck — which happens more often than you'd think when you're
killing processes during development. It stops the systemd service, force-kills any
stray Python processes, and cleans up PID files.
// what I learned
Building an HTTP server from scratch without a framework is surprisingly educational. You realize how much Flask and friends are doing for you when you have to implement cookie parsing, session management, multipart streaming, and JSON APIs by hand. It's not hard — Python's stdlib handles most of it — but it's humbling.
The trickiest part was the MJPEG stream. The camera pushes frames to a shared output
object, and each streaming client reads from it in a loop. Getting the threading right
— multiple clients, frame synchronization, clean disconnects — took a few iterations.
The final version uses a threading.Condition to wake up all clients
as soon as a new frame arrives.
I also learned that running a camera as a systemd service requires a
bit of care. The picam user needs to be in the video group,
the working directory has to exist before the service starts, and you really want
Restart=on-failure with a short delay so a crash during startup doesn't
spin forever.
// result
It's running. The Pi sits in a corner, the service starts on boot, and I can pull
up the stream from anywhere on my local network. Recordings land in ~/Videos
and are downloadable directly from the web interface. The schedule keeps it quiet
during the day and active at night — exactly what I wanted.
The obvious next step is making it accessible from outside the local network. A Cloudflare Tunnel would work — I already use one for Foundry VTT. For now, the local-only setup is exactly what I need.
[ Cet article n'est pas encore disponible en français. ]