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. ]