Files
hermes-config/skills/.archive/pihole-troubleshooting/SKILL.md
T
2026-07-12 10:17:17 -04:00

6.0 KiB

name, description
name description
pihole-troubleshooting Diagnose Pi-hole v6 when devices lose internet — query the FTL database, identify false-positive blocks from deep CNAME inspection, and fix connectivity-domain failures.

Pi-hole Troubleshooting

Triggers: "is Pi-hole working", "some devices have no internet", "DNS not resolving", "Pi-hole blocking too much", "devices can't connect after DNS change".

Pi-hole v6 stores all query history in a SQLite FTL database. Query it directly when the web UI is unreachable or you need raw data for pattern analysis.

Quick health check

docker ps --format "table {{.Names}}\t{{.Status}}" | grep pihole
docker exec pihole pihole status
dig +short google.com @<pi-hole-ip>

FTL database querying (Pi-hole v6)

The database is at /etc/pihole/pihole-FTL.db inside the container. Docker volumes typically live under /var/lib/docker/volumes/pihole_etc/_data/pihole-FTL.db but the /var/lib/docker/ directory requires root to traverse — copy the DB out with sudo first:

sudo cp /var/lib/docker/volumes/pihole_etc/_data/pihole-FTL.db /tmp/pihole-FTL.db
sudo chown $USER:$USER /tmp/pihole-FTL.db

Then query with Python (sqlite3 is not in the Pi-hole container and may not be on the host):

import sqlite3
conn = sqlite3.connect('/tmp/pihole-FTL.db')

Key queries

Recent activity by client:

SELECT client, count(*) FROM queries 
WHERE timestamp > strftime('%s','now','-1 hour') 
GROUP BY client ORDER BY count(*) DESC;

Check for blocked domains (status 1=gravity, 4=regex, 5=exact):

SELECT domain, count(*) FROM queries 
WHERE timestamp > strftime('%s','now','-1 hour') AND status IN (1,4,5)
GROUP BY domain ORDER BY count(*) DESC LIMIT 20;

Retry rate by client (>20% is suspicious):

SELECT client, count(*) as total,
  sum(CASE WHEN status=17 THEN 1 ELSE 0 END) as retried,
  round(100.0 * sum(CASE WHEN status=17 THEN 1 ELSE 0 END) / count(*), 1) as pct
FROM queries WHERE timestamp > strftime('%s','now','-24 hours')
GROUP BY client HAVING total > 20 ORDER BY pct DESC;

Pi-hole v6 status codes

Code Meaning
1 Gravity block (adlist match)
2 Forwarded to upstream
3 Cache hit
4 Regex denylist block
5 Exact denylist block
6 Upstream block
12 Already forwarded (cached forward)
14 Cached as blocked (from prior CNAME-chain inspection)
17 Retried (first attempt failed, retry succeeded)

Status 14 is the dangerous one — see Deep CNAME inspection pitfall below.

Primary pitfall: Deep CNAME inspection false positives

Symptoms: Some devices lose internet after switching DNS to Pi-hole. Devices that do strict connectivity checks (Android TV, Windows NCSI) are most affected. Pi-hole health check passes, DNS resolves fine from the server, but client devices think there's no internet.

Root cause: When CNAMEdeepInspect = true (default in v6), Pi-hole follows CNAME chains. If ANY domain in the chain matches a blocklist entry, the ENTIRE chain is cached as blocked (status 14). Common false-positive domains:

  • connectivitycheck.gstatic.com (Android TV/Shield connectivity check)
  • dns.msftncsi.com (Windows NCSI connectivity check)
  • ota.nvidia.com (Shield TV updates)
  • clients3.google.com, android.apis.google.com (Google Play Services)

The blocklists (e.g., StevenBlack) include subdomains like pagead.l.google.com — these are ad-specific, but deep CNAME inspection propagates the block up the chain to the parent l.google.com, which kills ALL services using Google infrastructure.

Detection: Query the FTL database for status 14 on these domains:

SELECT domain, count(*) FROM queries WHERE status=14 
GROUP BY domain ORDER BY count(*) DESC LIMIT 20;

If connectivitycheck.gstatic.com, dns.msftncsi.com, or google.com appear here → deep CNAME inspection is the cause.

Fix — whitelist the critical domains (preferred):

# Pi-hole v6: use 'allow', NOT 'pihole -w' (that's v5 syntax, broken)
docker exec pihole pihole allow connectivitycheck.gstatic.com
docker exec pihole pihole allow dns.msftncsi.com
docker exec pihole pihole allow clients3.google.com
docker exec pihole pihole allow ota.nvidia.com
docker exec pihole pihole allow android.apis.google.com

# Google Cast / Chromecast: mtalk domains break casting on Android TV & Shield
docker exec pihole pihole allow alt1-mtalk.google.com
docker exec pihole pihole allow alt2-mtalk.google.com
docker exec pihole pihole allow alt3-mtalk.google.com
docker exec pihole pihole allow alt4-mtalk.google.com
docker exec pihole pihole allow alt5-mtalk.google.com
docker exec pihole pihole allow alt6-mtalk.google.com
docker exec pihole pihole allow alt7-mtalk.google.com
docker exec pihole pihole allow alt8-mtalk.google.com

# Flush the DNS cache so cached blocks are cleared
docker exec pihole pihole reloaddns

Alternative fix — disable deep CNAME inspection:

Edit /etc/pihole/pihole.toml or set via environment: CNAMEdeepInspect = false. This stops the false positives but may allow some CNAME-cloaked ad/tracking domains through.

Other diagnostic checks

Is the device even on the network?

ping -c 3 <device-ip>
ip neigh show <device-ip>   # Check ARP status

STALE = was recently seen, FAILED = unreachable, REACHABLE = online.

Is Pi-hole rate-limiting?

docker exec pihole grep -i 'rate.limit' /var/log/pihole/FTL.log | tail -10

Default: 1000 queries per 60 seconds per client. If triggered, increase the limit or investigate the noisy client.

Check upstream DNS latency:

dig +time=3 google.com @8.8.8.8        # Direct to upstream
dig +time=3 google.com @<pi-hole-ip>   # Via Pi-hole

Upstream latency > 100ms can cause Pi-hole to retry queries (status 17).

Docker-specific notes

Pi-hole v6 container: pihole/pihole:latest (or dated tag). Container is minimal Alpine — no python3, no sqlite3. Query the DB from the host.

Find the database volume:

docker inspect pihole --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'