Pi-hole blocked my own analytics, and other DNS filter surprises
Blog · networking

Pi-hole blocked my own analytics, and other DNS filter surprises

A 'ban' that was my blocklist, visits that never counted, and internal names that vanished for 30 minutes after every reboot

“Google Analytics banned me.” That was my conclusion on 24 October 2026: the Analytics dashboard would not load at home, but worked fine over my VPN. Google had banned nobody. My own Pi-hole was answering 0.0.0.0 for four Google Analytics and Tag Manager domains, for every device in the house.

Running your own DNS filter is one of the best things you can do for a home network. It also means that every surprise on your LAN might be your own blocklist. This post covers three such surprises from my lab: an analytics dashboard that “banned” me, visits that were never counted, and internal names that stayed unreachable for about 30 minutes after every reboot even though the internet worked.

Contents · 8 sections

TL;DR #

  • If a website works over VPN or mobile data but not at home, ask your DNS filter first: pihole -q <domain>.
  • Blocklists such as StevenBlack’s block analytics and tag domains. Your own visits to your own sites are then never counted.
  • Allowlist narrowly: the dashboard you need, not the tracking domains, unless you really want the whole LAN tracked.
  • Test analytics from outside your DNS filter, with a real browser engine and real DNS.
  • If your router fails over to public resolvers when Pi-hole is down, internal names get NXDOMAIN, and clients may cache that for up to half an hour.
  • Never conclude “single point of failure” from one snapshot of a router’s configuration.

The setup #

The home router hands out itself as DNS server and forwards every LAN query to Pi-hole, which runs as a pod in the lab’s Kubernetes cluster. Pi-hole blocks ads and trackers with the StevenBlack unified hosts list and resolves the lab’s private internal zone.

The router also watches Pi-hole. A small watchdog checks that Pi-hole answers; if it stops, the router switches its upstream to public resolvers, and switches back when Pi-hole returns. Internet DNS keeps working when the workstation reboots. Remember that detail; it comes back later.

Surprise 1: “Google Analytics banned me” #

The symptom was convincing. The Google Analytics web interface failed to load from the LAN. Over the VPN it worked. A ban based on my home IP seemed the obvious explanation.

The VPN was the clue. My VPN path did not use Pi-hole, so it resolved Google’s domains normally. Asking Pi-hole directly showed the rest:

kubectl exec -n <pihole-namespace> <pihole-pod> -- pihole -q analytics.google.com
kubectl exec -n <pihole-namespace> <pihole-pod> -- pihole -q www.googletagmanager.com

The blocklist matched four domains: the Analytics web interface, the Tag Manager domain that serves gtag.js, and two collection domains that receive the hits. To every device in the house they resolved to 0.0.0.0.

The fix was one line, and one decision:

pihole allow analytics.google.com

Only the dashboard domain was allowlisted. The tag and collection domains stay blocked on purpose. Unblocking them would let Google’s tag load and report from every site that uses it, on every device in the house. That is exactly the tracking the blocklist exists to stop. Being able to read my own analytics does not require being tracked by everyone else’s.

One operational note: Pi-hole stores that allowlist in its own database on its data volume, not in my Git repository. That is why Pi-hole’s configuration is one of the backup sets the lab’s backup freshness check watches. An allowlist you cannot rebuild is configuration you can lose.

Surprise 2: my own visits never counted #

The same block has a second effect. Visits from inside the LAN never reach any Google Analytics property: the tag cannot load, so nothing is sent. Every time I checked my own site from home, I was invisible.

That combined badly with a small site’s real traffic. Analytics showed about one visitor, and I suspected broken tracking. A check from outside showed the tag loading and the page_view hit accepted. The edge logs explained the rest: of 265 addresses with browser-like user agents in a day, only 21 rendered a page, and most of those were bots. I wrote that analysis up in the post about Google Analytics with real consent mode.

The lesson for a DNS-filtered network: your own testing does not represent your visitors, in either direction.

How to test from outside the filter #

Testing “does my tag fire?” from a filtered LAN is harder than it sounds. My first attempt used headless Chrome on the host with resolver overrides and a network log; it never showed the tag load. What worked reliably was a Playwright container with host networking and explicit host entries for the Google domains, so the browser bypassed Pi-hole for exactly those names, plus a wait of about 12 seconds for the tag to load and fire:

docker run --rm --network host \
  --add-host www.googletagmanager.com:<public-ip> \
  --add-host region1.google-analytics.com:<public-ip> \
  -v "$PWD":/t -w /t mcr.microsoft.com/playwright:<version>-noble \
  sh -c 'npm i playwright@<version> >/dev/null && node check-tag.js https://www.example.com/'

check-tag.js opens the page, accepts consent if there is a banner, waits, and prints every request to the collection endpoint. Seeing a page_view request with a 2xx answer is the proof; a script tag in the HTML is not.

The alternative is to test from a network you do not filter: a phone on mobile data, or a remote machine.

Surprise 3: internal names gone for 30 minutes after a reboot #

In July a laptop on the Wi-Fi could not open the lab’s Grafana dashboard for about 30 minutes after the workstation rebooted. The server side was healthy within minutes: DNS answered, Traefik returned a redirect in 32 ms, the Pi-hole pod had restarted with the node. Then it fixed itself.

The cause was the router’s failover, the feature that keeps the internet working.

  1. The workstation reboots. Pi-hole goes down with it.
  2. The router’s watchdog notices and switches upstream DNS to public resolvers.
  3. The laptop asks for an internal name. Public resolvers have never heard of the private zone and answer NXDOMAIN.
  4. The laptop caches the negative answer. How long depends on the zone’s negative-caching TTL and the client’s own resolver behaviour, but 15 to 30 minutes is common.
  5. Pi-hole comes back, the router switches back. The laptop keeps using its cached “this name does not exist” until it expires.

Internet DNS never dropped, which is what the failover is for. Only internal names had a tail. The options are a shorter negative cache on the clients, a secondary resolver that also knows the internal zone, or simply knowing that the tail exists and flushing the client cache when you need it:

resolvectl flush-caches         # systemd-resolved
sudo dscacheutil -flushcache    # macOS
ipconfig /flushdns              # Windows

The misdiagnosis before the diagnosis #

Earlier I had declared the router’s DNS a single point of failure: I read its DNS settings once, while Pi-hole was up, saw a single upstream, and concluded that the whole LAN would lose DNS whenever the workstation rebooted. I was wrong. The watchdog that changes the upstream on failure lives in the router’s scheduler and monitoring scripts, not in the static DNS settings, and the router’s own logs showed it switching out and back.

A steady-state snapshot does not show dynamic failover. Before calling something a single point of failure, look for the automation that changes state when it fails, and for logs of it doing so.

Checklist #

  • When something works on VPN or mobile data but not at home, run pihole -q for its domains.
  • Allowlist the minimum: dashboards you use, not tags and collectors.
  • Keep Pi-hole’s allowlist and settings in a backup (or in Git, exported).
  • Test analytics and tags outside the filter, and confirm the collection request, not the script tag.
  • Expect your own visits to be missing from analytics.
  • If you fail over to public DNS, expect NXDOMAIN for internal names, and know how to flush client caches.
  • Check scripts, watchdogs and logs before declaring a single point of failure.

Frequently asked questions #

How do I check whether Pi-hole is blocking a domain? #

Run pihole -q <domain> on the Pi-hole host or container. It lists every blocklist and allowlist entry that matches. The query log in the web interface also shows blocked answers per client.

Does Pi-hole block Google Analytics? #

With common blocklists, yes: the tag, Tag Manager and collection domains are blocked, and sometimes the Analytics web interface too. Visitors behind such filters are not counted, and the dashboard may not load.

Should I allowlist Google Analytics domains in Pi-hole? #

Allowlist the dashboard domain if you need it. Allowlisting the tag and collection domains turns tracking back on for every device and every site on your network, which defeats the blocklist.

Why can’t my laptop resolve internal names after the DNS server restarts? #

During the outage another resolver answered “no such domain”, and the client cached that negative answer. It will retry after the negative-cache time expires; flushing the client’s DNS cache fixes it immediately.

The lesson #

A DNS filter changes what “the internet” looks like from inside your house, and it does so quietly. That is its job, and it is also why it is the first suspect for anything that works everywhere except at home. Ask it first, allowlist as little as possible, test from outside, and remember that the failover that protects your internet can briefly hide your own services.