Ads are still showing on a client, Pi-hole reports itself healthy, and the tunnel is up, so the instinct is to route that client's traffic through the Pi. That is the wrong turn. Filtering needs the questions, not the packets, and a client whose queries go to the ISP resolver stays unfiltered no matter where its traffic flows. Work this list in order and stop at the first step that fails, because a later step cannot tell you anything while an earlier one is broken.

1. Read the resolver the client is actually using

Ask the client, not the router. On Linux, resolvectl status prints the DNS servers per link and which link wins; /etc/resolv.conf on its own is usually a stub pointing at 127.0.0.53. On Windows, ipconfig /all lists the DHCP-provided servers per adapter, and on macOS scutil --dns shows the same thing by scope. If the answer is not Pi-hole's address, you have an assignment problem and step 2 is where it lives.

Two things defeat the system resolver even when it is set correctly. Browsers with their own secure DNS (Chrome's "Use secure DNS", Firefox's network.trr.mode) send queries over HTTPS to a public resolver and never open port 53. And a VPN client on the client itself may install a DNS server on its tunnel interface with a routing domain of ~., which makes that link authoritative for every name.

Test the path applications take, not the path a debug tool takes. resolvectl query example.com shows which link answered, and getent hosts example.com goes through the same NSS path an application does; on macOS, dscacheutil -q host -a name example.com reads the scoped resolvers that scutil --dns lists. A bare dig does not do this: it reads /etc/resolv.conf and skips per-link selection, so it can answer from Pi-hole while every application is using the pushed resolver, which is the exact failure this list exists to catch. Keep dig @192.168.1.53 for testing one specific server.

2. Confirm who hands out the answer

Assignment comes from whatever answers DHCP. On dnsmasq that is a single line, dhcp-option=6,192.168.1.53; on a router's web UI it is the DNS field in the LAN DHCP section. Clients have to renew before they see it. A device that has been up for days still holds the old lease, and a device with a manually typed resolver holds it forever. Renew on a test client instead of assuming propagation.

IPv6 is the half that gets missed. Router advertisements can carry their own DNS server (RDNSS), and on OpenWrt that is odhcpd's ra_dns option; a client that prefers IPv6 uses that server while the DHCPv4 option you just set sits unused. If you are not ready to serve IPv6 DNS, clear the DNS advertisement only: ra_dns in odhcpd, or the RDNSS option in whatever answers router advertisements. Turning off the whole advertisement also removes IPv6 addressing from the LAN, which is a much larger change than the one you meant to make, while clearing just the DNS option keeps addressing intact and makes the missing RDNSS visible in a packet capture.

3. Prove the query reaches Pi-hole

A query from the Pi proves nothing about the network path. Run it from the client and read the response code:

dig @192.168.1.53 example.com
dig @192.168.1.53 doubleclick.net

A timeout points at filtering or at a route; REFUSED points at an access rule; SERVFAIL points at the upstream resolver behind Pi-hole; a correct answer that never appears in the query log means the client asked something else. Check UDP and TCP 53, because large answers and some resolvers fall back to TCP, and publish both when Pi-hole runs in a container. A mapping that only covers UDP produces intermittent failures that look like packet loss.

Pi-hole's listening mode then decides whether it answers at all. In local-only mode dnsmasq ignores queries from sources that are not on a directly attached subnet, which is exactly what a client behind another router or arriving through a tunnel looks like: the client times out and the query never reaches the log. A query that does show up as REFUSED was rejected by an access list or a firewall rule instead. Permitting all origins is the usual fix, paired with a firewall rule that limits port 53 to your own subnets.

4. Check whether the tunnel replaced or swallowed the answer

This is the case both symptoms hide in. When the router is itself a VPN client, a pushed DNS option can override the LAN setting the moment the tunnel comes up: OpenVPN servers push dhcp-option DNS, and WireGuard profiles carry their own DNS entry. Read the router's effective resolver with the tunnel up, not before it, and ignore what the LAN page claims.

The other fault is reachability rather than replacement. If the tunnel's allowed prefixes include your LAN subnet, the router has a route that sends queries for 192.168.1.53 into the tunnel, and the far side has no route back to it: queries time out although the tunnel is healthy. The reverse happens when Pi-hole sits on a subnet the tunnel does not carry. Compare the address against the tunnel's allowed prefixes and the router's route table before you touch DNS again.

Pick one fix rather than both. Either keep the tunnel away from DNS, with pull-filter ignore "dhcp-option DNS" on OpenVPN and no LAN prefix in AllowedIPs on WireGuard, or accept the pushed resolver and give up local filtering for clients behind that router. Whichever you choose, write it next to the profile, because the next person to edit it will otherwise reverse the decision by accident.

5. Repeat the test after a reconnect

Query from a client, confirm that client appears in Pi-hole's log, then resolve a domain that should be blocked and check that you get the blocking answer instead of the real one. Reconnect the tunnel and run the same two queries, because a resolver setting that only survives until the next reconnect is the usual result of a fix applied at the wrong layer.

Leave a secondary resolver path that does not depend on the tunnel, so a Pi-hole outage degrades to unfiltered browsing instead of no browsing at all. Keep the previous DHCP and resolver values until the test passes twice; a change that breaks the household's internet at dinner is much harder to debug than one you can revert in a minute.

Nuŋlɔɖi siwo ku ɖe eŋu