Practical Home Tech

DNS · September 2026

Bringing Pi-hole into the network

Why I moved the house off third-party DNS, the UniFi setting that silently blocked my firewall rule, and how the Kids network ended up filtered harder than everyone else.

Why I added it

When I set up the VLANs, DNS on the Kids network went to OpenDNS FamilyShield. It caught the obvious stuff, and I didn't have to maintain a thing. The catch was that nothing stopped a device from using a different DNS server. A curious kid with a settings menu could get round the whole filter in about ten seconds.

I also didn't love that every DNS lookup in the house, on all three networks, was going to a third party. I wanted to see what was being looked up, decide for myself what got blocked, and not have that decision live on someone else's server.

Pi-hole sorts out the second problem completely, and gives me the tools to fix the first, although, as you'll see, I haven't fully closed that one yet. It runs on a Raspberry Pi 5 on Main. This post covers how I got it talking to the other two VLANs, what runs alongside it now, and the mistakes that cost me most of an evening each.

Getting the firewall to let it through

Pi-hole sits at 10.92.30.212 on Main. IoT and Kids needed to reach it on port 53, and at first they couldn't, because my firewall blocks everything between networks unless a rule allows it. That's the whole point of setting it up that way, and it did its job: nothing got through until I said so.

So I wrote the obvious rule: from the IoT Isolation zone (which IoT and Kids both sit in) to the Internal zone, TCP and UDP port 53, destination 10.92.30.212, allow. It didn't work. dig timed out from every device I tried, and the rule's hit counter sat on zero whatever I threw at it.

I spent a while assuming the rule itself was wrong: wrong port, wrong zone, or shadowed by something above it. It was none of those. Each network in UniFi has its own Isolate Network checkbox, completely separate from the firewall zones, and I'd ticked it when I first created the VLANs. With it on, a network can't reach anything else, and that's enforced before your firewall rules are even checked. My allow rule was fine; it just never got asked. Unticking it on IoT and Kids let the zone rules do their job.

With that off, and one more fix I'll come to further down, the rule worked. I checked it with dig from a device on each VLAN, and confirmed each query turned up in Pi-hole's own query log, rather than just trusting a clean-looking response.

Home Assistant needed rules of its own too. I later locked it down to specific devices instead of leaving it open to the whole zone, and letting Kids reach it is where I hit the same symptom a second time. More on that below.

Setting it up properly

Being reachable isn't the same as being used. Two more things had to be right before any of this did anything.

Pi-hole had to accept queries from other networks. Under Settings → DNS → Interface settings, the default is "Allow only local requests", which quietly ignores anything not on Pi-hole's own subnet. I set it to Permit all origins. Pi-hole's own docs warn that this is dangerous, but that warning is aimed at a Pi-hole sitting directly on the internet with port 53 open. Mine isn't: the UniFi firewall decides who can reach it, so it's the right setting here.

Every VLAN had to be told to use it. I nearly skipped this, assuming a working firewall rule was the whole job. In UniFi, each network's DHCP Name Server was set to Auto, which hands out the gateway's own address, not Pi-hole's. I switched IoT and Kids to Manual with 10.92.30.212, one network at a time. Then I renewed the lease on a test device on each network and checked which DNS server it had picked up. On Linux, nmcli connection down then up does it without a reboot, and resolvectl status shows the result.

The real proof isn't a clean dig response. It's the query turning up in Pi-hole's Query Log against the right client IP. A dig can succeed from a cached answer or a fallback server and tell you nothing.

One loose end: UniFi still hands out the gateway as a second DNS server alongside Pi-hole, tied to its "Auto default gateway" setting. I first wrote this off as harmless, on the basis that devices only use the second server if the first stops answering. That turns out not to be reliable. Plenty of phones and laptops will send queries to either server, or to whichever answers first. On most networks that just means the odd ad slipping through. On Kids, it's a gap in the filtering, so it's on the list at the end.

Cutting out the third party

Pi-hole decides what gets blocked, but out of the box it still forwards every allowed query to someone else's resolver. Google, in my case. Having gone to the trouble of running my own DNS, sending every lookup to Google felt like it missed the point.

The fix is Unbound, a recursive resolver. Instead of asking a third party, it looks each domain up itself, starting from the root DNS servers and working down. It's light enough that running it on the same Pi as Pi-hole makes no noticeable difference.

# /etc/unbound/unbound.conf.d/pi-hole.conf
server:
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    private-address: 10.92.10.0/24
    private-address: 10.92.20.0/24
    private-address: 10.92.30.0/24

The private-address lines are DNS rebinding protection. They tell Unbound to throw away any answer from the internet that points at an address inside my network, which stops a dodgy public domain being used to reach devices on my VLANs. My own local names are answered by Pi-hole, not Unbound, so they're unaffected. If you're copying this, Pi-hole's own Unbound guide covers all the private address ranges, not just mine, which is the safer default.

With Unbound listening on 127.0.0.1 port 5335, I pointed Pi-hole at it (Settings → DNS → Custom DNS servers: 127.0.0.1#5335) and unticked Google. To prove it was actually being used, and not just coincidentally still working, I looked up a domain that doesn't exist and watched Pi-hole's log:

forwarded nonexistent-test-domain-xyz123.com to 127.0.0.1#5335
reply nonexistent-test-domain-xyz123.com is NXDOMAIN

A made-up domain can't be sitting in a cache anywhere, and the log shows Pi-hole handing it to Unbound and Unbound coming back with NXDOMAIN (no such domain). That's about as clean a proof as you get. Unbound has no web interface, which is fine. It's meant to be invisible plumbing behind Pi-hole, and if something's wrong, Pi-hole's dashboard and log are still where I look first.

Filtering Kids harder than everyone else

This was the real point of the exercise: everyone gets a sensible baseline, and Kids gets more.

Pi-hole's Group Management does this without setting anything up per device. I added the whole Kids subnet, 10.92.20.0/24, as a client under Group Management → Clients, and created a Kids group for it. Each blocklist can then be assigned to a group, instead of applying to every network.

WhoBlocklists
Everyone (Default group)StevenBlack's unified hosts list: general ads and malware
Kids onlysomeonewhocares.org hosts list (broader ad and tracker blocking), plus a dedicated adult-content blocklist

I tested it rather than trusting the config: a known adult site loads from Main and is blocked outright from a device on Kids.

I've deliberately stopped there. Every extra list means longer updates, more memory, and a higher chance of a false positive breaking a site I actually use. A few well-chosen lists beat eight overlapping ones.

What went wrong

More than usual, if I'm honest.

The same symptom twice, with two different causes. Once Isolate Network was off, my Pi-hole rule started taking hits, but queries still timed out. The rule's Connection State was set to "Return Traffic" instead of "All", so it only allowed replies to connections that already existed and never allowed a new one. An evening later, I let Kids reach Home Assistant and got exactly the same symptom: the allow rule's hit counter climbing every time I tested, and the connection still timing out. This time the rule was fine. The automatically created return rule for the reply traffic had ended up below a broader block rule, so the replies never made it back. I burned real time looking at Home Assistant itself before I worked that out.

The lesson: if the allow rule is taking hits but the connection still fails, look at the reply traffic and the rule order first. I should have gone straight there the second time, instead of starting from scratch.

A dead end: I tried testing with homeassistant.local across VLANs. .local names work over mDNS, which is multicast and doesn't cross VLANs on its own, whatever your firewall rules say. It cost me a round of confusion before I remembered that's exactly what VLANs are for.

One thing I haven't nailed down. Since switching to Unbound, Pi-hole's diagnosis page logs the odd CONNECTION_ERROR against 127.0.0.1#5335, roughly every four to five hours, with nothing in Unbound's logs or the system journal at those times. A forum thread suggested adding incoming-num-tcp: 25 to Unbound's config for the same symptom. I tried it, and it's still happening. But nobody in the house has hit a single failed lookup in weeks of it recurring, so it's starting to look like a harmless quirk in how Pi-hole hands queries to Unbound rather than a real fault. I'm keeping an eye on it rather than calling it closed just because nobody's complained.

What's next

One thing I looked at and deliberately didn't do: running a password manager on the same Pi. It's tempting, since the box is always on, but every VLAN in the house is set up to talk to it for DNS. Putting my most sensitive data on my most reachable box would undo a fair bit of the segmentation work from the last post. If I do it, it's going on its own hardware, reached over Tailscale.

If you've hit the return-rule ordering issue yourself, or found something that actually fixes the Unbound errors, I'd like to hear about it: [email protected]