Skip to content
Infrastructure

Health monitoring for Pi-hole and Unbound

By Victor Da Luz
pihole unbound uptime-kuma dns monitoring homelab health-checks

I run Pi-hole with Unbound as the recursive resolver across six instances: a master and a cluster replica on Proxmox LXCs, plus four per-VLAN replicas on Raspberry Pis. Uptime Kuma needs to do more than hit a pretty status page: it has to tell me whether DNS resolution still works through each box.

This post matches what the automation actually deploys today, not a fuller wishlist.

The problem

A green HTTPS check on /admin/ only proves the web server and TLS path you configured. It does not prove:

  • Clients can resolve names through Pi-hole and Unbound.
  • Forwarding to Unbound is healthy.
  • The resolver chain has not wedged while the UI still loads.

Checking raw port 53 is better than nothing, but it still does not prove a query completes end-to-end. I wanted at least one monitor that issues a real DNS query through each Pi-hole’s address.

What is implemented now

In the homelab repo, each Pi-hole entry in state/services.yaml carries a monitoring.kuma list. Ansible applies those definitions to Uptime Kuma with the kuma01-monitors role (the lucasheld.uptime_kuma modules). In practice that is two monitors per Pi-hole instance:

  1. HTTPS admin UI: https against /admin/ on port 8443, with TLS verification relaxed for the self-signed cert Pi-hole uses internally.
  2. DNS: resolve a stable test name (in state this is an A query for example.com) using that instance as the resolver. The role resolves .lan hostnames to IPs before pushing the monitor, because the Kuma container does not rely on mDNS/LAN DNS for those targets.

That is the full set in code. There is no separate HTTP check against /admin/api.php, and no dedicated TCP monitor on Unbound’s port (5335 in my setup). Unbound is still exercised indirectly: if Unbound is down or not forwarding, the DNS-type monitor fails while the UI might stay green.

Older automation in the same ecosystem used a Jinja-rendered monitors.yaml.j2 that also emitted only those two shapes per Pi-hole node (web + DNS). Anything beyond that lives in notes or head, not in the template.

Why two layers are enough for a first cut

The pair is deliberately redundant in a useful way:

  • If HTTPS fails, the service or host is probably unreachable or TLS is broken.
  • If HTTPS passes and DNS fails, I stop looking at “is the server up?” and start looking at Pi-hole’s forwarder, Unbound, or upstream resolution.

It is not complete coverage of every failure mode (see below), but it is honest coverage: two checks that map to “can I reach the UI?” and “can this instance resolve DNS for clients?”

Numbers

Six Pi-hole instances × two monitors each → twelve Pi-hole-related monitors in Kuma from this pattern, before you count unrelated services.

Operational defaults

Intervals and timeouts come from each monitoring.kuma entry in state (for example 60s interval on the checks I ship today). The UI monitors carry a critical tag where defined so alerting lines up with the rest of the stack.

What I am not claiming yet

Ideas that are easy to describe in prose but not in the current automation include:

  • A dedicated Pi-hole API probe (anything like summaryRaw on api.php).
  • A TCP check on Unbound’s port separate from the DNS query.
  • Performance or DNSSEC synthetics.

Those are reasonable next steps; they are just not what the playbooks generate right now.

Verification

After a run of the monitor playbook, I expect two children under each Pi-hole group in Kuma: the HTTPS check and the DNS check. Breaking Unbound or Pi-hole forwarding should flip the DNS monitor while leaving the admin UI check passing. That is the split I wanted when I added the second layer.

What I learned

Real DNS beats “port 53 is open.” A socket listening on 53 does not prove recursion, caching, or forwarding through Unbound. A DNS monitor that completes a query does.

State-driven monitors beat hand-clicked duplicates. Pushing from state/services.yaml keeps every Pi-hole instance covered the same way as the fleet grows (two LXCs plus four Pis, today).

Document the gap. It is easy for services.yaml comments or a blog outline to get ahead of the Jinja2 or Ansible that applies the config. I am standardizing on: if the automation does not create it, the post should not claim it.

Next steps (maybe)

API checks, an Unbound port monitor, response-time trends, DNSSEC probes, or synthetic ad-domain blocks are all fair game once they land in state and in the role that applies to Kuma.

Disclosure: This article contains affiliate links. If you purchase through these links, I may earn a commission at no extra cost to you.

Related reading

Infrastructure

Verifying DNS Leak Protection in the Homelab

Understanding DNS leaks, why they matter for privacy, and how to verify that your Pi-hole and Unbound setup isn't leaking queries to your ISP or third-party DNS providers.

Read

Ready to Transform Your Career?

Let's work together to unlock your potential and achieve your professional goals.