157 lines
6.0 KiB
Markdown
157 lines
6.0 KiB
Markdown
---
|
|
name: pihole-troubleshooting
|
|
description: 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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```python
|
|
import sqlite3
|
|
conn = sqlite3.connect('/tmp/pihole-FTL.db')
|
|
```
|
|
|
|
### Key queries
|
|
|
|
**Recent activity by client:**
|
|
```sql
|
|
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):**
|
|
```sql
|
|
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):**
|
|
```sql
|
|
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:
|
|
|
|
```sql
|
|
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):**
|
|
|
|
```bash
|
|
# 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?**
|
|
```bash
|
|
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?**
|
|
```bash
|
|
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:**
|
|
```bash
|
|
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:
|
|
```bash
|
|
docker inspect pihole --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
|
|
```
|