Keeping Tabs on a Countertop Cat

When my cat started helping himself to the butter dish at 2 AM, I knew it was time for surveillance. I could have bought an off-the-shelf pet cam, but I already owned a mostly idle Raspberry Pi 4 Model B. The plan was simple: connect a compatible camera module, expose the local video feed to the internet, and wrap it in a hard authentication layer.

What You Need

  • A Raspberry Pi with a camera module and an operating system installed
  • Shell access (via SSH or VNC)
  • A domain name with DNS control
  • A Cloudflare account with Teams and Tunnel access
  • An internet connection

I will skip the Raspberry Pi operating system setup, but you need a running system with internet connectivity before proceeding.

Turning the Pi into a Webcam Server

The first step is physically connecting the camera module to the Raspberry Pi. The official camera setup guide covers this in detail. After verifying the hardware works, we use Motion, a program that turns a camera module into a web server.

Install Motion from the official repositories:

sudo apt-get update
sudo apt-get install motion

Configure Motion with sudo nano /etc/motion/motion.conf. The default configuration exposes the feed at http://localhost:8081. You can change the port here if you like. To view the feed from another machine on the same LAN, replace the listening address with the Pi's IP address.

Finally, enable Motion as a daemon so it restarts automatically with the system:

sudo systemctl enable motion
sudo systemctl start motion

The local camera is a closed circuit at this point. To check on Ziggy from anywhere, we need a secure tunnel. Cloudflare's Tunnel product is ideal here. It avoids router configuration and firewall hole-punching, which is a risky alternative that can expose your internal network.

The official Cloudflare Tunnel installation documentation works on most Linux distributions, but Raspberry Pi OS uses the armv7l 32-bit architecture and doesn't have a `cloudflared` package in its repositories. While `cloudflared` is open source and could be compiled from source, downloading the binary directly is faster.

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm
sudo cp cloudflared-linux-arm /usr/local/bin/cloudflared
sudo chmod +x /usr/local/bin/cloudflared

Now we need to create a tunnel and give it an ingress rule. The tunnel's UUID is generated during setup, and the credential file path is specified in the `cloudflared.yml` configuration. The ingress section maps a domain (e.g., `camera.imohak.com`) to the Motion server running on port 8081. A `404` rule serves as a catch-all fallback.

Once the config is ready, point the DNS CNAME record for your chosen subdomain at the tunnel. This is done from the Cloudflare dashboard.

Open the domain in a browser and you'll see video you can watch from anywhere. But so can anyone else who guesses the URL. The feed contains everything that happens inside your house — leaving it without authentication is not an option.

Locking the Feed Down with Cloudflare Access

Two authentication options present themselves. Motion has built-in username and password authentication, but that is another password to remember and potentially a liability if Motion's authentication code is ever discovered to be vulnerable. Cloudflare's Teams Access product is a better fit. It lets me enforce single sign-on via Google rather than a separate password, and it adds flexible policy controls.

First, set up Cloudflare for Teams at dash.teams.cloudflare.com. Choose a team name during onboarding.

We then need two configurations within the Teams dashboard:

  1. A login method — an identity provider that allows Google-based authentication.
  2. An application — the URL of the pet cam, protected by that identity provider.

To add a login method, go to Configuration > Authentication and click +Add under the Login tab. Choose Google from the provider list. Cloudflare's identity provider integration guide explains how to create a Google Cloud application and generate a ClientID and Client Secret to complete the setup.

To protect the camera URL, navigate to Access > Applications and click Add an application. Select Self-hosted. In the next screen, choose a name (like "camera") and set the URL session duration and subdomain (e.g., camera.imohak.com). Google is our identity provider, and since it's the only authentication method, we can enable Instant Auth to skip a provider selection screen.

Finally, add a policy: after Google authenticates the user, we can enforce an email address rule so only a specific address is accepted. Cloudflare supports other, more complex policy rules as needed.

The browser now presents a Google authentication page first. Once signed in, I can watch Ziggy's kitchen counter escapades live from anywhere.