← Back to Projects
Active

Local DNS Sinkhole on Raspberry Pi

A Raspberry Pi Zero 2 W running Pi-hole for local DNS filtering, backed by a small Python maintenance workflow for safer updates, health checks, and local reporting.

raspberry-pipi-holednspythonhome-lab

A home-network DNS sinkhole built on a Raspberry Pi Zero 2 W. Every device on my network resolves DNS through the Pi, which blocks ads, trackers, and known-bad domains before they ever load, and gives me a single place to see what my network is actually talking to.

Why I built this

I wanted network-wide ad and tracker blocking without installing anything on individual devices. A DNS sinkhole does that at the source: point the network’s DNS at the Pi, and the filtering applies to phones, laptops, and smart-home gear automatically.

But the real motivation was the part that comes after the install. A Pi-hole is only useful if it stays healthy, and a Raspberry Pi Zero 2 W is a small machine that needs occasional care: OS updates, Pi-hole updates, package cleanup. I didn’t want to rediscover those steps from memory every couple of weeks, and I didn’t want to blindly automate something that touches DNS for my whole house. So the project is really two things: the sinkhole itself, and a small, transparent maintenance workflow I can trust and actually understand.

What it is

  • Hardware: Raspberry Pi Zero 2 W, running Raspberry Pi OS (Debian-based).
  • Software: Pi-hole installed natively on the OS (not in Docker), acting as the DNS server for the whole network.
  • Maintenance tooling: a small Python script I run from my Mac that connects to the Pi over SSH and walks through maintenance in reviewable phases, appending the results to a single local report file.

The design goal throughout was simple, local, and inspectable: no cron jobs, no remote orchestration, nothing that runs unattended against my DNS without me watching.

Accessing the dashboard

Pi-hole ships with an admin web interface. On my network I reach it at the Pi’s local address followed by /admin and log in with the admin password. From there the useful views are:

  • Query log: what every device is resolving in real time, which is genuinely eye-opening the first time you see how chatty modern apps are.
  • Dashboard: total queries, percentage blocked, and the top blocked domains.
  • Blocking toggle: temporarily disable filtering when something legitimate gets caught.
  • Teleporter: export a full backup of the configuration. This one matters: I always export a Teleporter backup before updating Pi-hole, so a bad upgrade is a quick restore instead of a rebuild.

The maintenance routine

I run maintenance roughly every two weeks, and after any major change or troubleshooting session. Instead of one big automated run, the workflow is broken into phases I step through deliberately:

# 1. Pre-check: confirm the Pi and Pi-hole are healthy before touching anything
python3 pi-hole.py --phase precheck

# 2. Export a Teleporter backup from the web UI (before any update)

# 3. Reviewed package updates over SSH, read each summary before confirming
sudo apt update
sudo apt full-upgrade
sudo apt autoremove
sudo apt autoclean

# 4. Update Pi-hole itself (Core, Web, FTL)
pihole -up

# 5. Post-check: confirm everything still resolves and no services failed
python3 pi-hole.py --phase postcheck

A few decisions in the script are there on purpose:

  • No stored secrets. Passwords are entered at runtime with Python’s getpass, so nothing is hardcoded or echoed to the terminal.
  • SSH host key verification. The script checks the Pi’s host key so I’m not silently connecting to the wrong machine.
  • Updates are never blind. It deliberately does not run pihole -up or apt full-upgrade -y automatically. Pi-hole gets backed up first, and I review each upgrade summary before pressing Y.
  • One running report. Every run appends command output, status, and errors to a single report file, so I have a maintenance journal to check when something breaks later.

What I keep an eye on

The post-check exists to answer one question: is the network still healthy? The things I actually watch for:

  • pihole status shows Pi-hole active and blocking enabled.
  • dig google.com @127.0.0.1 returns a valid answer on the Pi, and resolution works from a client on the network.
  • systemctl --failed shows no failed services.
  • pihole -g (gravity / blocklist update) completes cleanly.
  • Kernel and firmware upgrades can look frozen on a Pi Zero 2 W running off an SD card. That’s normal. I wait it out rather than interrupting a package install mid-write.

Not every warning is a fire. A non-blocking message I’ve seen after a Pi-hole update is local: FTL_PID_FILE: readonly variable. If status is healthy and DNS resolves, I log it and move on instead of panicking.

Pros and cons

What works well

  • Network-wide blocking with zero config on individual devices.
  • Real visibility into what my network is doing via the query log.
  • A maintenance process that’s repeatable, reviewable, and easy to fix because I understand every step.
  • Backups before updates mean low-risk upgrades.

The trade-offs

  • It’s a single point of DNS for the network. If the Pi goes down, resolution needs a fallback.
  • A Pi Zero 2 W is modest hardware; large kernel upgrades are slow.
  • Maintenance is still manual by design. That’s a deliberate choice for trust and transparency, but it does mean I have to actually run it.

Where it goes next

The workflow is intentionally not “finished.” Things I may add when there’s a real reason: SSH keys instead of password auth, a single prompt when the SSH and sudo passwords match, clearer flagging of failed steps, and report rotation once the log grows large. The guiding rule is the one I wrote at the top of the repo for future me: keep Pi-hole healthy, keep the process simple, and don’t overcomplicate a small home task just because a fancier option exists.