Your Pi-hole can't see DNS over HTTPS, and other leaks around it

Router DHCP points at the Pi-hole. Dashboard full of blocked ads. Network filtered. Done?

Open the query log and ask the opposite question. Not what got blocked. Which devices are missing?

A phone that streamed video for an hour and left three lines. A TV that never shows up at all. A laptop whose browser quietly stopped asking your Pi-hole anything. The dashboard can't tell you, because it only counts what arrived.

What a Pi-hole actually receives

Pi-hole is a DNS server. pihole-FTL answers on the standard DNS port, and a classic lookup is a small packet addressed there. Send it there and Pi-hole counts it, blocks it, logs it. Send anything else and Pi-hole isn't part of the conversation.

DNS over HTTPS is anything else. RFC 8484 puts the same DNS message inside an ordinary HTTPS request, media type application/dns-message, so to the network it's a web request to port 443 instead of a lookup to port 53. Section 8.1 lists that as a feature: the default HTTPS port, mixed with other HTTPS traffic, makes interference and traffic analysis harder. Harder for an attacker on the path. Also harder for the person who put a Pi-hole on the path on purpose.

The RFC's own GET example, a lookup of the A record for www.example.com:

:method = GET
:scheme = https
:authority = dnsserver.example.net
:path = /dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB
accept = application/dns-message

So a browser that talks DoH to its own resolver never knocks on the Pi-hole's door. In Firefox that's the TRR code: TRR-first tries DoH and falls back to plain DNS, TRR-only doesn't, and both hang off the network.trr.mode pref (Firefox source docs). Neither asks you.

Check the canary first

Mozilla's polite answer is a canary domain. Before Firefox turns DoH on by itself, it looks up use-application-dns.net through the system resolver, and a negative answer tells it the network filters DNS, so it should stand down. Pi-hole gives that answer: NXDOMAIN for A and AAAA queries of that name. In v6 the switch is dns.specialDomains.mozillaCanary in /etc/pihole/pihole.toml, default true per the config reference.

Test it instead of believing it (swap in your Pi-hole's address):

dig A use-application-dns.net @192.168.1.2 | grep status
dig AAAA use-application-dns.net @192.168.1.2 | grep status
grep -n mozillaCanary /etc/pihole/pihole.toml

You want status: NXDOMAIN twice and mozillaCanary = true. NOERROR with addresses means something else answered, or somebody switched it off.

A canary is a request, not a lock. It covers the automatic rollout; a person who picks a DoH provider by hand never asks anyone. Chromium behaves differently: its docs say the automatic mode upgrades to the DoH server of your current DNS provider. A resolver on a private address inside your LAN has none, so as I read it, Chrome in its default mode stays with your Pi-hole.

Find who skips it

Two lists: the router's DHCP leases (who is on the network) and the Pi-hole query log filtered by client (who talks to the Pi-hole). Anyone clearly in use on the first and absent from the second found another resolver. The usual suspects: DoH inside a browser or app, a resolver hard-coded into the device, a resolver the router announced over IPv6, plain DNS to an address that isn't yours.

The Pi-hole can't help you sort them, it's the blind one. Capture at the router or on a mirror port (the interface name is OpenWrt's):

tcpdump -ni br-lan 'port 53 and not host 192.168.1.2'
tcpdump -ni br-lan 'tcp port 853'

The first prints DNS going anywhere but the Pi-hole. Hard-coded resolvers and IPv6 clients show up here (if the Pi-hole has an IPv6 address too, add another and not host). The second catches DNS over TLS, which has a port of its own; the DNS-over-TLS RFC assigns the number you see in that filter. A dedicated port is easy to see and easy to block. DoH went the other direction on purpose.

DoH has no capture filter. A device that's busy, missing from the log, and silent on both plain DNS and DNS over TLS is a suspect for encrypted DNS over the ordinary HTTPS port, and a suspect isn't a verdict. The verdict lives on the device. In Firefox, look at network.trr.mode in about:config. These two values mean DoH is answering your lookups:

2   TRR-first
3   TRR-only

Closing what can be closed

Reject outbound plain DNS and DNS over TLS from every client except the Pi-hole. The OPNsense page in Pi-hole's docs walks through it: allow the Pi-hole's DNS traffic, reject the rest, Pi-hole's rule first. The same page says plainly that it doesn't cover blocking DoH or DoT. Expect complaints, since the docs warn that devices with hard-coded servers may stop working. Some TV will sulk. That tells you it was using them.

Then IPv6. Pi-hole's page for the Fritz!Box shows how to announce the Pi-hole's IPv6 address in router advertisements (RA/RDNSS) and DHCPv6. A client can learn its resolver from there and never touch the IPv4 one you configured so carefully. If your router can't announce what you want, switch the announcement off, or accept that IPv6 clients are partly outside the filter.

DoH itself you can't close like this. Blocklists of known endpoints exist, but the DoH RFC is designed so that an endpoint looks like any other HTTPS host, and I wouldn't build on a list. Where the Pi-hole's view matters more to you than the browser's, turn the browser's own DoH off. On a network of five devices that's a fine fix.

DoH does belong on this network: behind the filter, not around it. Pi-hole's dnscrypt-proxy guide has dnscrypt-proxy listen on localhost on a port of its own, with Pi-hole forwarding to it:

sudo pihole-FTL --config dns.upstreams '["127.0.0.1#5053"]'

Now lookups leave the house encrypted, and the filter still sees them first.

The leaks that stay

Encrypting the upstream hides your lookups from the wire, not from the resolver. The RFC on DNS privacy considerations is blunt about it: the recursive resolver sees your address and your queries, and an encrypted transport doesn't shrink what it knows.

The name leaks again in the TLS handshake. Server name indication travels in cleartext in the ClientHello, as the RFC on SNI encryption lays out, so a hidden lookup is followed by a visible name anyway. Encrypted Client Hello became an RFC in 2026 and closes that for servers that publish a configuration in DNS. Until your sites do, hiding DNS hides one of two.

And your own log: Pi-hole keeps query history for 91 days by default (database.maxDBdays in the same config reference). Every name every device asked for, in one place; the log aggregator post is built on that record. Anyone who gets into the box reads it as easily as you do, so protect the Pi the way you would protect any log server.

Away from the shelf

Everything above assumes the device sits on your Wi-Fi. Carry it into a hotel, an airport lounge, a cafe, and the Pi-hole stays on the shelf. The device joins, DHCP hands it that network's own resolver (the RFC on DNS privacy considerations notes the network you join has historically set your default one), and from then on the owner of the network, or whoever it forwards to, sees each name the device looks up, next to the address it was given. Plain DNS is cleartext, the same complaint this blog made about syslog. No blocklist applies and there's no query log for you to read later.

For the version without dig and tcpdump, the kind you can send to someone who will never run a Pi-hole, there is a plain-language rundown of what a hotel network can actually see and what it can't. Its best point is not about DNS at all: the bigger risk in a hotel is a fake network named after the hotel, with a login page that asks for a room number or a card.

DoH in the browser hides the names from that resolver and hands them to the DoH provider instead. The network still sees destination addresses and the server name in every handshake.

The other way is a tunnel home. Pi-hole's WireGuard guide for routing everything has the client config point DNS at the Pi-hole's address inside the tunnel and send all IPv4 and IPv6 traffic into it:

DNS = 10.100.0.1
AllowedIPs = 0.0.0.0/0, ::/0

The IPv6 entry after the comma is the part people drop; without it the IPv6 half of your traffic bypasses the tunnel. With it, the hotel sees one encrypted flow to your home address, and your blocklists come along.

It doesn't make the observer disappear. Whoever sits at the far end of any tunnel inherits the cafe's view; here that's your home ISP, and the sites see your home address. The observer moved, it didn't go away.

A Pi-hole filters the traffic that chooses to pass through it. That was always the deal.