My DNS setup was working perfectly. Pi-hole handled ad blocking, dnscrypt-proxy encrypted DNS traffic, and Quad9 resolved every query my network generated. I had been running this setup for a long time, and I rarely thought about it. Then I started thinking, if DNS filtering and encryption were already in my local stack, why was I letting a third-party resolver see every query my network made? Every lookup was outsourced to someone else’s infrastructure. That simple thought was enough to make me replace the public resolver with a self-hosted one — Unbound. I ran it for a week, expecting better privacy. And at the end of the week, I got a much clearer understanding of what my DNS was actually doing.
Pi-hole was doing its job, but it wasn't actually resolving DNS
The missing piece
When I set up Pi-hole on my home Debian server, my main motive was to block ads network-wide, not because I hate ads on websites or apps. I am a publisher and indie developer. I implement ads on my app and website as a monetization source. But some websites have more ads than the content we visit to consume. I could live with the ads, but people like my mother, who had a smaller digital footprint, struggled to navigate through apps without accidentally clicking ads. I deployed Pi-hole on my home server and implemented it network-wide via my dual-WAN gateway, our online experience became clean.
Pi-hole solved the visibility problem, but it left another one untouched. Once I noticed Pi-hole was only filtering requests as a watchman — filtering requests locally, but handing them off the moment they left my network. I started exploring more options. Honestly, Pi-hole was doing what it was supposed to do. It wasn’t Pi-hole’s job to check on the queries after they left Pi-hole's scope, whether they were traveling in plaintext or whether they were resolving or not. To fix that, I added another layer to the setup as DNS-over-HTTPS to encrypt the queries until they reach the resolver. I self-hosted dnscrypt-proxy to encrypt the DNS requests and configured Quad9 and Cloudflare DNS resolvers.
Once fully set up, one of the issues of my setup of the plaintext DNS problem was solved, but I still had another one. The DNS requests were still reaching third-party DNS resolvers, in my case Quad9 and Cloudflare. Even though the traffic was encrypted, Quad9 and Cloudflare still received every query and performed the lookup. The flow was like:
Device -> Pi-hole -> dnscrypt-proxy -> Quad9/CF (root servers → TLD servers → authoritative servers)
I was already deep into self-hosted alternatives. I started exploring options so that I could own the last part of the loop, the DNS. The DNS was still outsourced, and I wasn’t ready to accept it. It didn’t mean I didn’t trust Quad9 or Cloudflare, but I was more inclined to own that part. If public resolvers were still handling every lookup, what would happen if I removed them entirely? That was when I thought of trying out Unbound. Rather than forwarding requests upstream, Unbound could resolve them itself. That sounded like a fun weekend experiment.
The privacy benefit was real, but the independence mattered more
One less middleman
With Pi-hole and dnscrypt-proxy, the DNS providers, Quad9/Cloudflare, were sitting between my network and the wider DNS system. They were handling every recursive lookup on my behalf, and their logs had a complete picture of every domain I visited. But once Unbound was running, that public resolver disappeared, and no single DNS provider was handling every lookup anymore. But at the same time, I did lose the outbound encryption layer. The new flow of my traffic became:
Device -> Unbound -> root servers -> TLD servers -> authoritative servers
With Unbound handling resolution, each part of the resolution chain now goes to a different party. The root servers only knew I was asking where to find .com. The TLD servers knew which domain I was looking for. And authoritative servers only saw the requests of their own domain. No single party saw the chain, only their respective segments. It didn’t make me anonymous or magically hide my internet activity, but it simply removed one centralized observer.
Finally, my network had no dependency on any public resolvers, such as Quad9 and Cloudflare. I wasn’t switching because those services are bad, but because I wanted DNS to be another service my home lab owned. I was already self-hosting ingress, storage, and applications, and suddenly, the resolver itself felt like part of my self-hosted infrastructure. Not only was I happy with the privacy angle, but I was amazed by the stats.
I set it up on Saturday and left it running for two days of real traffic. It already resolved 25,845 queries in two days, with 63,641 outbound UDP packets, a 148ms median recursion time, and 6,596 cache hits. The 63,641 UDP out vs 25,845 queries resulted in roughly 2.5 outbound packets per query, indicating that each query touched a different part of the resolution chain independently. It wasn’t a theory anymore and not something tested in a simulated lab; I could see real numbers in my day-to-day workflow.
I stopped relying on Quad9 or Cloudflare to do the work and watched my own server do it instead.
The biggest upgrade was what I didn't expect Unbound to do
The unexpected win
Okay, I replaced the public resolver with a self-hosted solution. Everything worked out all right, but in the meantime, I forgot about the primary feature I aimed for when I opted for Pi-hole — ad blocking. I was telling a colleague that I was going to run both Unbound and Pi-hole together, since Unbound alone wasn't handling ad blocking. Then he told me that although there was not a direct way to adblock using Unbound, I could create a custom adblock list to work alongside DNS resolution.
I immediately wrote a script that downloaded Steven Black's unified host list of 83,600 domains from its GitHub repo and saved it as a blocklist.conf file on each run.
#!/bin/bash
BLOCKLIST_URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
OUTPUT="/etc/unbound/unbound.conf.d/blocklist.conf"
echo "Downloading blocklist..."
echo "server:" > /tmp/blocklist.conf
curl -s "$BLOCKLIST_URL" | grep '^0\.0\.0\.0' | awk '{print " local-zone: \""$2"\" redirect\n local-data: \""$2" A 0.0.0.0\""}' >> /tmp/blocklist.conf
echo "Installing blocklist..."
mv /tmp/blocklist.conf "$OUTPUT"
echo "Restarting Unbound..."
systemctl restart unbound
echo "Done. $(grep -c 'local-zone' $OUTPUT) domains blocked."
Once the domain list was loaded, I saw a sudden change in the memory usage; memory jumped from around 7MB to almost 88MB once the blocklist was loaded. But the numbers after two days of usage made that memory jump easy to forget.
With adblock and DNSSEC, the stats looked like 1,760 NXDOMAIN responses, 3,406 DNSSEC-validated responses, and 1,953 prefetches. This meant Unbound was blocking ads and trackers while also cryptographically verifying that responses had not been tampered with. And it was the first time I understood what those numbers actually meant.
The experiment wasn't entirely perfect. I could also see 148 exceeded requests; that meant the queue filled up 148 times under load. Not surprising for a server already running 19 containers. The surprises didn't end here; it also had native split-DNS functionality, which I had previously handled through Pi-hole. I configured it to resolve my local Jellyfin domain when accessing from the same local network, bypassing Cloudflare Tunnel. And why Jellyfin first? Because of Cloudflare's ToS restrictions on streaming.
There were a few things I did miss about Pi-hole. The most important one was the visibility. When I opened the Pi-hole dashboard, I knew exactly what was happening in my network. Not just raw numbers but genuinely readable data. Client activity, query types, exact blocked domains, and response times. I did have stats in the Unbound setup, but they were just raw numbers, accessible only via the terminal.
Even if I return to Pi-hole or move to something else entirely, I now understand a piece of internet infrastructure that I had been outsourcing and largely ignoring.
One week later: was it worth it?
A week with Unbound convinced me that it is not a no-brainer replacement for Pi-hole that everyone should rush into. Replacing Pi-hole with Unbound didn't suddenly make my internet faster or expose any flaw in my setup. Everything in my setup was already doing what it was supposed to do. But it did convince me that most homelabbers outsource DNS resolution without thinking twice.
If you want zero upstream dependency and can live with terminal-only management, Unbound is worth running, even temporarily. But if you rely on dashboards for visibility and need a simpler, GUI-managed setup, then Pi-hole is right for you. And if you want the best of both, you can always run Pi-hole alongside Unbound. Sometimes the best homelab projects aren’t the ones that add new features — they’re the ones that reveal how much infrastructure you’ve been taking for granted.
Unbound
- OS
- Windows, Linux, macOS
- Price model
- Free
Unbound is an open-source caching, recursive DNS resolver.