Pi-hole: Complete Network-Wide Ad Blocking Guide
Deploy network-level ad blocking for all devices. DNS management, privacy protection, and custom blocklists.
Introduction
Pi-hole is a powerful network-level ad blocker that acts as a DNS sinkhole, protecting every device on your network from advertisements, trackers, and malicious domains without requiring individual installation on each device. In this comprehensive guide, you’ll learn how to deploy, configure, and optimize Pi-hole in your homelab, complete with automation strategies and integration with other services.
What You’ll Learn
- How DNS-level ad blocking works and why it’s effective
- Multiple deployment methods (Docker, bare metal, VMs)
- Configuring blocklists and managing false positives
- Setting up local DNS and DHCP services
- Monitoring performance and optimizing configuration
- AI-powered automation for intelligent threat detection
- Integration with monitoring stacks and home automation
Why Pi-hole?
Network-Wide Protection: A single Pi-hole instance protects all connected devices, including smart TVs, IoT devices, and mobile apps that don’t support traditional ad blockers.
Privacy by Design: By blocking tracking domains at the DNS level, you prevent data exfiltration before it happens. When self-hosted, you maintain complete control over your DNS queries.
Performance Gains: Ads never download, saving bandwidth and reducing page load times. Devices use less CPU and memory, extending battery life on mobile devices.
Educational Value: Pi-hole provides insight into network traffic patterns and hands-on experience with DNS, Docker, and system administration.
Prerequisites
Hardware Requirements:
- CPU: 1GHz or faster
- RAM: 512MB minimum (1GB+ recommended)
- Storage: 2GB minimum (20GB+ recommended for logs)
- Network: Ethernet connection strongly recommended
Knowledge Requirements:
- Basic Linux command line familiarity
- Understanding of home network configuration
- Access to router admin interface
- (Optional) Docker experience for container deployment
Time Commitment:
- Initial setup: 30-45 minutes
- Configuration: 1-2 hours
- Ongoing maintenance: 15 minutes per week
Understanding DNS-Level Ad Blocking
How DNS Works
Before diving into Pi-hole, let’s understand the Domain Name System (DNS). When you type ”www.example.com” into your browser:
- Your device sends a DNS query: “What’s the IP address for www.example.com?”
- A DNS resolver responds with the IP address: “93.184.216.34”
- Your device connects to that IP address to load the website
This happens for every domain - legitimate websites, ad servers, and tracking domains alike.
The DNS Sinkhole Technique
Pi-hole intercepts DNS queries and implements a “sinkhole” approach:
User clicks website
↓
Device requests: ads.example.com
↓
Pi-hole checks blocklist
↓
Domain found in blocklist? → YES → Return 0.0.0.0 (null address)
↓ ↓
NO Device can't connect
↓ ↓
Forward to upstream DNS Ad never loads
↓
Return legitimate IP
↓
Content loads normally Key Advantages:
- Works across all applications, not just browsers
- No CPU/memory impact on client devices
- Blocks ads in native apps and smart TVs
- Single point of control for entire network
- Bandwidth saved by preventing downloads
Blocklists and Gravity
Blocklists (also called “adlists”) are curated text files containing domain names of known advertising and tracking servers, maintained by security researchers and the community:
# Example blocklist content
ads.example.com
tracker.network.com
analytics.advertising.com
telemetry.data-collector.com Gravity is Pi-hole’s compiled database that merges multiple blocklists into a single searchable index. When you run “Update Gravity,” Pi-hole:
- Downloads all configured blocklists
- Removes duplicates and validates entries
- Compiles into an optimized SQLite database
- Makes it ready for instant lookups
Installation Methods
Choosing Your Deployment Method
| Method | Best For | Pros | Cons |
|---|---|---|---|
| Docker | Existing Docker infrastructure | Easy updates, isolated environment | Networking complexity for DHCP |
| Bare Metal | Dedicated servers | Minimal overhead, full control | OS-specific configuration |
| Virtual Machine | VM environments | Complete isolation, snapshots | Higher resource usage |
Method 1: Docker Deployment (Recommended)
Docker provides the easiest path to deployment with simple updates and clean isolation from other services.
Basic Docker Compose Configuration
Create a directory for your Pi-hole deployment:
mkdir -p ~/homelab/pihole
cd ~/homelab/pihole Create docker-compose.yml:
version: '3.8'
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
- "443:443/tcp"
environment:
TZ: 'America/Chicago'
WEBPASSWORD: 'ChangeThisToASecurePassword'
FTLCONF_LOCAL_IPV4: '192.168.1.100' # Your server's IP
DNS1: '1.1.1.1'
DNS2: '8.8.8.8'
DNSMASQ_LISTENING: 'all'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
networks:
- pihole_network
restart: unless-stopped
cap_add:
- NET_ADMIN
dns:
- 127.0.0.1
- 1.1.1.1
networks:
pihole_network:
driver: bridge Deploy Pi-hole:
# Start the container
docker-compose up -d
# Verify it's running
docker-compose ps
# Check logs
docker-compose logs -f pihole Docker Configuration with DHCP
If you want Pi-hole to handle DHCP for your network, use host networking:
version: '3.8'
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
network_mode: "host"
environment:
TZ: 'America/Chicago'
WEBPASSWORD: 'ChangeThisToASecurePassword'
FTLCONF_LOCAL_IPV4: '192.168.1.100'
DNS1: '1.1.1.1'
DNS2: '8.8.8.8'
DHCP_ACTIVE: 'true'
DHCP_START: '192.168.1.201'
DHCP_END: '192.168.1.251'
DHCP_ROUTER: '192.168.1.1'
PIHOLE_DOMAIN: 'lan'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
restart: unless-stopped
cap_add:
- NET_ADMIN Important: You must disable DHCP on your router before enabling it in Pi-hole.
Docker with Unbound (Privacy-Enhanced)
For maximum privacy, run Pi-hole with Unbound as a recursive DNS resolver:
version: '3.8'
services:
unbound:
container_name: unbound
image: mvance/unbound:latest
ports:
- "5335:53/tcp"
- "5335:53/udp"
volumes:
- './unbound:/opt/unbound/etc/unbound'
networks:
- pihole_network
restart: unless-stopped
pihole:
container_name: pihole
image: pihole/pihole:latest
depends_on:
- unbound
ports:
- "53:53/tcp"
- "53:53/udp"
- "80:80/tcp"
- "443:443/tcp"
environment:
TZ: 'America/Chicago'
WEBPASSWORD: 'ChangeThisToASecurePassword'
FTLCONF_LOCAL_IPV4: '192.168.1.100'
DNS1: '172.20.0.2#5335' # Unbound container
DNS2: 'no'
volumes:
- './etc-pihole:/etc/pihole'
- './etc-dnsmasq.d:/etc/dnsmasq.d'
networks:
pihole_network:
ipv4_address: 172.20.0.3
restart: unless-stopped
cap_add:
- NET_ADMIN
dns:
- 127.0.0.1
- 1.1.1.1
networks:
pihole_network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16 Create the Unbound configuration:
mkdir -p unbound Create unbound/unbound.conf:
server:
# Enable verbosity for debugging
verbosity: 0
# Network interface
interface: 0.0.0.0
port: 53
do-ip4: yes
do-udp: yes
do-tcp: yes
# May be set to yes if you have IPv6 connectivity
do-ip6: no
# Use this only when you downloaded the list of primary root servers!
# root-hints: "/opt/unbound/etc/unbound/root.hints"
# Trust glue only if it is within the server's authority
harden-glue: yes
# Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
harden-dnssec-stripped: yes
# Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
use-caps-for-id: no
# Reduce EDNS reassembly buffer size.
edns-buffer-size: 1232
# Perform prefetching of close to expired message cache entries
prefetch: yes
# One thread should be sufficient
num-threads: 1
# Ensure kernel buffer is large enough to not lose messages in traffic spikes
so-rcvbuf: 1m
# Ensure privacy of local IP ranges
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: fd00::/8
private-address: fe80::/10 Method 2: Bare Metal Installation
For dedicated hardware or maximum performance, install directly on the operating system.
Ubuntu/Debian Installation
Update your system:
sudo apt update
sudo apt upgrade -y
sudo apt install curl -y Install Pi-hole:
curl -sSL https://install.pi-hole.net | bash The installer will guide you through:
- Interface Selection: Choose your primary network interface (usually
eth0) - Upstream DNS Provider: Select from Cloudflare, Google, OpenDNS, etc.
- Block Lists: Default lists are recommended for beginners
- IP Protocols: Enable both IPv4 and IPv6 if your network supports it
- Static IP Warning: Ensure your Pi-hole has a static IP address
- Web Admin Interface: Enable this (recommended)
- Web Server: Install lighttpd (yes)
- Logging: Enable query logging (yes)
- Privacy Mode: Choose your preferred level
After installation:
# Set admin password
pihole -a -p
# Check status
pihole status
# View version
pihole -v Post-Installation Configuration
Configure a static IP address (if not already set):
# Edit netplan configuration (Ubuntu 20.04+)
sudo nano /etc/netplan/00-installer-config.yaml Example configuration:
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
nameservers:
addresses:
- 127.0.0.1
- 1.1.1.1 Apply changes:
sudo netplan apply Method 3: Virtual Machine Deployment
Proxmox LXC Container (Most Efficient)
Create an LXC container in Proxmox:
Download Ubuntu 22.04 LXC template
Create container with:
- CPU cores: 2
- Memory: 2048 MB
- Storage: 20 GB
- Network: Bridge to your LAN
- Static IP: Assign static IP (e.g., 192.168.1.100)
Start container and access console
Run bare metal installation steps above
VMware/VirtualBox VM
Create a new VM:
- OS: Ubuntu Server 22.04 LTS
- vCPU: 2 cores
- RAM: 2 GB
- Disk: 30 GB
- Network: Bridged adapter
Install Ubuntu Server, then follow bare metal installation steps.
Accessing the Web Interface
Once installed, access the Pi-hole admin dashboard:
http://pi.hole/admin Or by IP address:
http://192.168.1.100/admin Login with the password you set during installation.
Initial Configuration
Dashboard Overview
The Pi-hole dashboard provides real-time metrics:
Total Queries: DNS queries received in the last 24 hours Queries Blocked: Number of queries matching blocklists Percent Blocked: Percentage of total queries blocked Blocklist Last Updated: When Gravity was last updated Domains on Blocklist: Total number of blocked domains
Configuring Network Devices
You have three options for directing traffic to Pi-hole:
Option 1: Configure Router (Recommended for Beginners)
Most home routers allow you to set custom DNS servers:
- Access your router’s admin interface (usually
192.168.1.1or192.168.0.1) - Navigate to DHCP settings (location varies by router)
- Find DNS server configuration
- Set Primary DNS to your Pi-hole IP (e.g.,
192.168.1.100) - Set Secondary DNS to
1.1.1.1or leave blank - Save settings
Clients will receive Pi-hole DNS on their next DHCP lease renewal.
To force renewal on clients:
# Windows
ipconfig /release
ipconfig /renew
# Linux
sudo dhclient -r
sudo dhclient
# macOS
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder Option 2: Manual Configuration Per Device
For testing or specific devices:
Windows:
- Settings → Network & Internet → Change adapter options
- Right-click your network adapter → Properties
- Select “Internet Protocol Version 4 (TCP/IPv4)” → Properties
- Select “Use the following DNS server addresses”
- Set Preferred DNS:
192.168.1.100(your Pi-hole) - Set Alternate DNS:
1.1.1.1
macOS:
- System Preferences → Network
- Select your connection → Advanced
- DNS tab → Add Pi-hole IP
Linux:
- Edit
/etc/resolv.conf:nameserver 192.168.1.100 nameserver 1.1.1.1
Option 3: Pi-hole as DHCP Server
For advanced users who want centralized management:
Admin Panel → Settings → DHCP Configuration:
- DHCP Active: Enable
- DHCP Authoritative Mode: Enable
- Range Start:
192.168.1.201 - Range End:
192.168.1.251 - Router (Gateway):
192.168.1.1 - Lease Time:
24 hours
Critical: Disable DHCP on your router before enabling in Pi-hole to avoid conflicts.
Verifying Pi-hole is Working
Test DNS resolution:
# Should resolve to Pi-hole IP
nslookup pi.hole
# Test ad domain (should return 0.0.0.0)
nslookup ads.google.com
# Test legitimate domain
nslookup google.com Check query log in web interface:
Admin Panel → Query Log You should see your test queries appearing in real-time.
Setting Your Timezone
Settings → System → Timezone Select your timezone to ensure logs and statistics show correct times.
Creating Configuration Backups
Regular backups protect against data loss:
Settings → Teleporter → Backup This downloads a .tar.gz file containing:
- Blocklist configuration
- Custom DNS records
- DHCP reservations
- Whitelist/blacklist entries
- Group assignments
Store backups securely and test restoration periodically.
Managing Blocklists
Understanding Blocklist Strategy
Quality Over Quantity: Use 5-10 well-maintained lists rather than 50 random lists. Too many blocklists:
- Slow down gravity updates
- Increase memory usage
- Create redundancy (same domains multiple times)
- Raise false positive rates
Recommended Approach: Start with basic lists and add more based on your specific needs.
Recommended Blocklists
Essential Blocklists (Start Here)
1. Steven Black’s Unified Hosts
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts - ~200,000 domains
- Combines multiple reputable sources
- Excellent balance of coverage and accuracy
- Updated regularly
2. AdGuard DNS Filter
https://v.firebog.net/hosts/AdguardDNS.txt - High-quality ad blocking
- Minimal false positives
- Well-tested
3. EasyList (Browser Ads)
https://v.firebog.net/hosts/Easylist.txt - Focuses on browser-based ads
- Widely used in browser extensions
- Good complement to other lists
Advanced Blocklists
4. Tracking & Telemetry
https://v.firebog.net/hosts/Easyprivacy.txt - Blocks tracking and analytics
- Improves privacy significantly
5. Malware Domains
https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt - Known malware and phishing domains
- Security-focused
6. OISD (Comprehensive)
https://small.oisd.nl/ - ~1.5 million domains
- Highly comprehensive
- Good curation
- Requires more resources
Adding Blocklists
Navigate to:
Admin Panel → Group Management → Adlists Steps:
- Click “Add a new adlist”
- Paste the URL
- Add description (e.g., “Steven Black - Unified Hosts”)
- Click “Add”
- Repeat for additional lists
- Click “Tools → Update Gravity”
Gravity update process:
[i] Neutrino emissions detected...
[✓] Downloading blocklist source files
[✓] Parsing blocklist sources
[✓] Deduplicating domains
[✓] Building gravity database
[✓] Gravity updated successfully Managing False Positives with Allowlists
Some legitimate services get caught in blocklists. Common examples:
Microsoft Services:
www.msftncsi.com
*.msftconnecttest.com Smart Home Devices:
*.amazon.com
*.amazonaws.com Streaming Services:
www.youtube.com
*.googlevideo.com Adding to Allowlist
Via Web Interface:
Admin Panel → Domains → Whitelist (Exact) Enter domain and click “Add to Whitelist”
Via Command Line:
pihole -w domain.com
# Whitelist multiple domains
pihole -w domain1.com domain2.com domain3.com
# Whitelist with regex
pihole --regex-whitelist '.*.example.com$' Advanced: Regex Filtering
Regex allows pattern-based blocking/allowing:
Common Regex Patterns
Block all subdomains of a domain:
^(.+.)?adnetwork.com$ Block multiple similar domains:
^(ads|tracking|telemetry).example.com$ Block domains containing specific keywords:
.*analytics.* Allow exceptions to broad rules:
^(?!important).*.ads.com$ Add regex filters:
Admin Panel → Domains → Regex Filter Group Management (Advanced)
Create different filtering profiles for different devices:
Admin Panel → Group Management → Groups Example Setup:
Group: Kids Devices
- Strict ad blocking
- Social media blocking
- Adult content blocking
- Assigned to: Tablets, game consoles
Group: Work Devices
- Basic ad blocking
- Allow business domains
- Minimal tracking blocking
- Assigned to: Work laptops
Group: IoT Devices
- Telemetry blocking
- Cloud service access
- No social media needed
- Assigned to: Smart TVs, cameras
Assign devices to groups via MAC address or client name.
Local DNS and DHCP Features
Local DNS Records
Pi-hole can resolve internal hostnames without querying external DNS servers.
Use Cases
- Access homelab services by name:
homeassistant.local→192.168.1.50 - Short aliases:
nas→192.168.1.40 - Development environments:
dev.myapp.local→192.168.1.100
Adding A Records
Admin Panel → Local DNS → DNS Records Example entries:
| Domain | IP Address | Description |
|---|---|---|
| homelab.local | 192.168.1.100 | Main server |
| nas.local | 192.168.1.40 | NAS device |
| homeassistant.local | 192.168.1.50 | Home Assistant |
| pihole.local | 192.168.1.100 | Pi-hole itself |
| router.local | 192.168.1.1 | Router |
Via dnsmasq configuration (advanced):
# Create custom configuration file
sudo nano /etc/dnsmasq.d/02-custom-dns.conf Add entries:
address=/homelab.local/192.168.1.100
address=/nas.local/192.168.1.40
address=/homeassistant.local/192.168.1.50 Restart Pi-hole:
pihole restartdns Adding CNAME Records
CNAME records create aliases pointing to existing records:
Admin Panel → Local DNS → CNAME Records Examples:
| Domain | Target | Result |
|---|---|---|
| media.local | nas.local | media.local → 192.168.1.40 |
| home.local | homeassistant.local | home.local → 192.168.1.50 |
| dns.local | pihole.local | dns.local → 192.168.1.100 |
Conditional Forwarding
Enable seamless integration between Pi-hole and your router’s DNS:
Settings → DNS → Conditional Forwarding Configuration:
- Enable: Check
- Local network in CIDR:
192.168.1.0/24 - IP address of DHCP server:
192.168.1.1 - Local domain name:
lan(or your custom domain)
How it works:
Client queries: computer.lan
↓
Pi-hole checks local DNS records
↓
Not found? → Forward to router at 192.168.1.1
↓
Router's DHCP provides hostname
↓
Response returned to client This allows router-assigned hostnames to work while Pi-hole handles ad blocking.
DHCP Server Configuration
When to Use Pi-hole DHCP
Advantages:
- Better client identification in logs (hostnames vs IP addresses)
- Automatic DNS registration for all clients
- Centralized network management
- Custom DHCP options
Disadvantages:
- Single point of failure if Pi-hole goes down
- Requires disabling router DHCP
- More complex failover setup
DHCP Configuration
Settings → DHCP Settings:
DHCP Active: ✓
DHCP Authoritative Mode: ✓
IPv4 Settings:
Range from: 192.168.1.201
Range to: 192.168.1.251
Router (gateway): 192.168.1.1
Lease time: 24 hours
DHCP Options:
Domain: lan Static DHCP Reservations
Reserve specific IPs for important devices:
Settings → DHCP → Static DHCP Leases Example:
| MAC Address | IP Address | Hostname |
|---|---|---|
| aa:bb:cc:dd:ee:01 | 192.168.1.50 | homeassistant |
| aa:bb:cc:dd:ee:02 | 192.168.1.40 | nas |
| aa:bb:cc:dd:ee:03 | 192.168.1.200 | printer |
Benefits:
- Critical services always have same IP
- Easier firewall rule management
- Consistent DNS resolution
Performance Monitoring and Optimization
Understanding Dashboard Metrics
Query Types
Cached Queries: Answered from Pi-hole’s memory
- Fastest response time (1-5ms)
- No upstream DNS query needed
- Indicates effective caching
Blocked Queries: Matched blocklists
- Instant response (0.0.0.0)
- No bandwidth used
- Ads/trackers never load
Forwarded Queries: Sent to upstream DNS
- Response time depends on upstream (10-50ms)
- Legitimate domains not in cache
Regex Blocked: Matched regex patterns
- Slightly slower than exact matches
- More flexible filtering
Key Performance Indicators
Queries Per Second (QPS)
- Light usage: 1-10 QPS
- Medium home: 10-50 QPS
- Heavy usage: 50-100+ QPS
Block Percentage
- Typical: 10-30%
- High (>50%): May indicate overly aggressive blocking
- Low (<5%): Consider adding more blocklists
Cache Hit Ratio
- Good: 30-50%
- Excellent: >50%
- Indicates effective caching strategy
Performance Optimization
Optimizing Cache Configuration
Edit dnsmasq configuration:
sudo nano /etc/dnsmasq.d/05-pihole-custom.conf Add optimizations:
# Increase cache size (default: 10000)
cache-size=20000
# Cache negative responses
no-negcache=false
# Set minimum TTL for caching
min-cache-ttl=300 Restart DNS:
pihole restartdns Blocklist Optimization
Identify Underperforming Lists:
Admin Panel → Group Management → Adlists Look at “Last updated” and note any lists that:
- Fail to update regularly
- Take excessive time to download
- Have zero contribution to blocking
Consolidation Strategy:
- Start with 3-5 high-quality lists
- Monitor blocking effectiveness for 1 week
- Add specialized lists if needed (malware, tracking)
- Remove lists that contribute <1% of blocks
Check Gravity Performance:
# Measure gravity update time
time pihole -g Target: <60 seconds for update completion
System Resource Optimization
Memory Usage:
# Check Pi-hole memory usage
docker stats pihole # For Docker
htop # For bare metal (filter by pihole-FTL) Typical usage: 50-200MB RAM
CPU Usage: Pi-hole should use minimal CPU (<5%) during normal operation. High CPU indicates:
- Excessive queries per second
- Inefficient regex patterns
- System resource constraints
Disk I/O:
# Monitor disk usage
df -h
# Check database size
ls -lh /etc/pihole/pihole-FTL.db Long-Term Statistics with InfluxDB
For historical analysis beyond Pi-hole’s built-in 24-hour window:
InfluxDB Integration
Create monitoring-stack.yml:
version: '3.8'
services:
influxdb:
container_name: influxdb
image: influxdb:2.7-alpine
ports:
- "8086:8086"
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: admin
DOCKER_INFLUXDB_INIT_PASSWORD: SecurePassword123
DOCKER_INFLUXDB_INIT_ORG: homelab
DOCKER_INFLUXDB_INIT_BUCKET: pihole
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: YourSuperSecretToken
volumes:
- influxdb-data:/var/lib/influxdb2
networks:
- monitoring
pihole-exporter:
container_name: pihole-exporter
image: ekofr/pihole-exporter:latest
environment:
PIHOLE_HOSTNAME: 192.168.1.100
PIHOLE_API_TOKEN: YourPiholeAPIToken
INTERVAL: 30s
PORT: 9617
ports:
- "9617:9617"
networks:
- monitoring
depends_on:
- influxdb
grafana:
container_name: grafana
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
GF_INSTALL_PLUGINS: grafana-clock-panel,grafana-simple-json-datasource
volumes:
- grafana-data:/var/lib/grafana
networks:
- monitoring
depends_on:
- influxdb
volumes:
influxdb-data:
grafana-data:
networks:
monitoring:
driver: bridge Deploy:
docker-compose -f monitoring-stack.yml up -d Access Grafana at http://localhost:3000 (admin/admin)
API Usage for Metrics
Get your API token:
Settings → API → Show API token Common API Endpoints
Summary Statistics:
curl "http://pi.hole/admin/api.php?summaryRaw&auth=YOUR_TOKEN" Response:
{
"dns_queries_today": 45000,
"ads_blocked_today": 8500,
"ads_percentage_today": 18.9,
"unique_clients": 12,
"unique_domains": 2300,
"gravity_last_updated": {
"absolute": 1703001600
}
} Top Blocked Domains:
curl "http://pi.hole/admin/api.php?topItems=10&auth=YOUR_TOKEN" Client Activity:
curl "http://pi.hole/admin/api.php?getQuerySources&auth=YOUR_TOKEN" Query Logs:
curl "http://pi.hole/admin/api.php?getAllQueries&auth=YOUR_TOKEN" AI Automation and Advanced Features
Intelligent Blocklist Management
Use machine learning to optimize your blocklists based on actual query patterns.
Pattern Analysis Script
Create analyze_blocks.py:
#!/usr/bin/env python3
"""
Analyze Pi-hole query patterns to identify new ad/tracking domains
and suggest blocklist optimizations.
"""
import requests
import json
from collections import Counter, defaultdict
from datetime import datetime
import re
PIHOLE_API = "http://192.168.1.100/admin/api.php"
API_TOKEN = "YOUR_API_TOKEN_HERE"
def get_query_data():
"""Fetch all queries from Pi-hole API"""
url = f"{PIHOLE_API}?getAllQueries&auth={API_TOKEN}"
response = requests.get(url)
return response.json()
def analyze_blocked_domains(queries):
"""Analyze blocked domains for patterns"""
blocked_domains = []
for query in queries.get('data', []):
if query[4] in ['2', '3']: # Status codes for blocked queries
domain = query[2]
blocked_domains.append(domain)
# Find common patterns
patterns = defaultdict(list)
for domain in blocked_domains:
parts = domain.split('.')
if len(parts) >= 2:
# Group by second-level domain
base_domain = '.'.join(parts[-2:])
patterns[base_domain].append(domain)
return patterns
def suggest_regex_patterns(patterns, min_count=5):
"""Suggest regex patterns for common domain groups"""
suggestions = []
for base, domains in patterns.items():
if len(domains) >= min_count:
# Common subdomains
subdomains = [d.split('.')[0] for d in domains if d.count('.') > 1]
if len(set(subdomains)) >= 3:
suggestion = {
'base_domain': base,
'count': len(domains),
'regex': f'^(.+\.)?{re.escape(base)}$',
'example_domains': domains[:5]
}
suggestions.append(suggestion)
return sorted(suggestions, key=lambda x: x['count'], reverse=True)
def identify_new_trackers(queries):
"""Identify potentially malicious domains based on query patterns"""
suspicious = []
for query in queries.get('data', []):
domain = query[2]
# Long, random-looking subdomains (potential DNS tunneling)
if len(domain) > 50:
parts = domain.split('.')
if any(len(part) > 20 for part in parts):
suspicious.append({
'domain': domain,
'reason': 'Suspiciously long subdomain',
'length': len(domain)
})
# Many numeric characters (possible DGA malware)
if sum(c.isdigit() for c in domain) > len(domain) * 0.4:
suspicious.append({
'domain': domain,
'reason': 'High numeric ratio (possible DGA)',
'numeric_ratio': sum(c.isdigit() for c in domain) / len(domain)
})
return suspicious
def main():
print("Fetching query data from Pi-hole...")
queries = get_query_data()
print("\n=== Analyzing blocked domains ===")
patterns = analyze_blocked_domains(queries)
print(f"\nFound {len(patterns)} unique base domains")
print("\n=== Regex Pattern Suggestions ===")
suggestions = suggest_regex_patterns(patterns)
for i, suggestion in enumerate(suggestions[:10], 1):
print(f"\n{i}. Base Domain: {suggestion['base_domain']}")
print(f" Blocked Count: {suggestion['count']}")
print(f" Suggested Regex: {suggestion['regex']}")
print(f" Example Domains:")
for domain in suggestion['example_domains']:
print(f" - {domain}")
print("\n=== Suspicious Domain Detection ===")
suspicious = identify_new_trackers(queries)
if suspicious:
print(f"\nFound {len(suspicious)} potentially suspicious domains:")
for item in suspicious[:10]:
print(f"\n Domain: {item['domain']}")
print(f" Reason: {item['reason']}")
else:
print("\nNo suspicious domains detected.")
# Generate report
report = {
'timestamp': datetime.now().isoformat(),
'total_patterns': len(patterns),
'regex_suggestions': len(suggestions),
'suspicious_domains': len(suspicious),
'recommendations': []
}
# Add recommendations
if len(suggestions) > 5:
report['recommendations'].append(
"Consider adding regex patterns for top 5 domain groups"
)
if len(suspicious) > 0:
report['recommendations'].append(
f"Investigate {len(suspicious)} suspicious domains for potential threats"
)
# Save report
with open('pihole_analysis_report.json', 'w') as f:
json.dump(report, f, indent=2)
print(f"\n\nReport saved to pihole_analysis_report.json")
if __name__ == "__main__":
main() Usage:
# Install dependencies
pip3 install requests
# Run analysis
python3 analyze_blocks.py Automated Threat Detection
Monitor for DNS-based attacks and anomalies:
DNS Anomaly Detector
Create dns_monitor.py:
#!/usr/bin/env python3
"""
Real-time DNS anomaly detection for Pi-hole
Detects DNS tunneling, DGA malware, and suspicious query patterns
"""
import requests
import time
import json
from collections import defaultdict, deque
from datetime import datetime
import statistics
class DNSMonitor:
def __init__(self, pihole_ip, api_token):
self.pihole_ip = pihole_ip
self.api_token = api_token
self.api_url = f"http://{pihole_ip}/admin/api.php"
self.query_history = deque(maxlen=1000)
self.domain_stats = defaultdict(lambda: {'count': 0, 'first_seen': None})
def fetch_recent_queries(self):
"""Fetch recent queries from Pi-hole"""
url = f"{self.api_url}?getAllQueries&auth={self.api_token}"
try:
response = requests.get(url, timeout=10)
return response.json()
except Exception as e:
print(f"Error fetching queries: {e}")
return None
def detect_dns_tunneling(self, domain):
"""Detect potential DNS tunneling attempts"""
# Characteristics of DNS tunneling:
# 1. Very long domain names
# 2. High entropy (random-looking)
# 3. Frequent queries to same base domain
if len(domain) > 100:
return True, "Extremely long domain name"
# Check for high entropy in subdomains
parts = domain.split('.')
for part in parts[:-2]: # Exclude TLD and base domain
if len(part) > 30:
# Check if subdomain looks random
unique_chars = len(set(part))
if unique_chars > len(part) * 0.6:
return True, f"High entropy subdomain: {part}"
return False, None
def detect_dga(self, domain):
"""Detect Domain Generation Algorithm (DGA) malware"""
# DGA domains typically have:
# 1. High ratio of consonants to vowels
# 2. Random-looking character sequences
# 3. Short to medium length
parts = domain.split('.')
if len(parts) < 2:
return False, None
subdomain = parts[0]
# Check length
if not (8 <= len(subdomain) <= 20):
return False, None
# Calculate consonant/vowel ratio
vowels = set('aeiou')
vowel_count = sum(1 for c in subdomain if c.lower() in vowels)
consonant_count = sum(1 for c in subdomain if c.isalpha() and c.lower() not in vowels)
if consonant_count > 0:
ratio = vowel_count / consonant_count
if ratio < 0.3: # Very few vowels
return True, f"Suspicious consonant/vowel ratio: {ratio:.2f}"
# Check for numeric characters
digit_count = sum(1 for c in subdomain if c.isdigit())
if digit_count > len(subdomain) * 0.4:
return True, f"High numeric content: {digit_count}/{len(subdomain)}"
return False, None
def detect_high_frequency(self, domain):
"""Detect unusually high query frequency"""
stats = self.domain_stats[domain]
# Update stats
stats['count'] += 1
if stats['first_seen'] is None:
stats['first_seen'] = datetime.now()
# Calculate queries per minute
time_delta = (datetime.now() - stats['first_seen']).total_seconds()
if time_delta > 0:
qpm = stats['count'] / (time_delta / 60)
if qpm > 100: # More than 100 queries per minute
return True, f"High frequency: {qpm:.1f} queries/minute"
return False, None
def analyze_queries(self, queries):
"""Analyze queries for threats"""
threats = []
if not queries or 'data' not in queries:
return threats
for query in queries.get('data', []):
if len(query) < 3:
continue
domain = query[2]
timestamp = query[0]
# Run detection algorithms
is_tunneling, tunnel_reason = self.detect_dns_tunneling(domain)
is_dga, dga_reason = self.detect_dga(domain)
is_high_freq, freq_reason = self.detect_high_frequency(domain)
threat = None
if is_tunneling:
threat = {
'domain': domain,
'type': 'DNS Tunneling',
'reason': tunnel_reason,
'timestamp': timestamp,
'severity': 'HIGH'
}
elif is_dga:
threat = {
'domain': domain,
'type': 'DGA Malware',
'reason': dga_reason,
'timestamp': timestamp,
'severity': 'HIGH'
}
elif is_high_freq:
threat = {
'domain': domain,
'type': 'High Frequency',
'reason': freq_reason,
'timestamp': timestamp,
'severity': 'MEDIUM'
}
if threat:
threats.append(threat)
return threats
def monitor(self, interval=60):
"""Continuous monitoring loop"""
print(f"Starting DNS monitoring on {self.pihole_ip}")
print(f"Checking every {interval} seconds...")
while True:
try:
queries = self.fetch_recent_queries()
threats = self.analyze_queries(queries)
if threats:
print(f"\n[{datetime.now()}] THREATS DETECTED:")
for threat in threats:
print(f"\n ⚠️ {threat['type']} - {threat['severity']}")
print(f" Domain: {threat['domain']}")
print(f" Reason: {threat['reason']}")
# Save to log file
with open('dns_threats.log', 'a') as f:
for threat in threats:
f.write(json.dumps(threat) + '\n')
else:
print(f"[{datetime.now()}] No threats detected")
time.sleep(interval)
except KeyboardInterrupt:
print("\nMonitoring stopped by user")
break
except Exception as e:
print(f"Error during monitoring: {e}")
time.sleep(interval)
def main():
# Configuration
PIHOLE_IP = "192.168.1.100"
API_TOKEN = "YOUR_API_TOKEN_HERE"
CHECK_INTERVAL = 60 # seconds
monitor = DNSMonitor(PIHOLE_IP, API_TOKEN)
monitor.monitor(interval=CHECK_INTERVAL)
if __name__ == "__main__":
main() Run as a service:
# Make executable
chmod +x dns_monitor.py
# Run in background
nohup python3 dns_monitor.py &
# Or create systemd service
sudo nano /etc/systemd/system/dns-monitor.service Service file:
[Unit]
Description=Pi-hole DNS Anomaly Monitor
After=network.target
[Service]
Type=simple
User=pihole
WorkingDirectory=/opt/dns-monitor
ExecStart=/usr/bin/python3 /opt/dns-monitor/dns_monitor.py
Restart=always
[Install]
WantedBy=multi-user.target Enable and start:
sudo systemctl enable dns-monitor
sudo systemctl start dns-monitor Automated Allowlist Management
Automatically manage allowlists based on user behavior:
Create auto_allowlist.py:
#!/usr/bin/env python3
"""
Automatically manage Pi-hole allowlists based on query patterns
Suggests domains to allowlist when blocking causes issues
"""
import requests
import json
from collections import Counter
from datetime import datetime, timedelta
class AutoAllowlist:
def __init__(self, pihole_ip, api_token):
self.pihole_ip = pihole_ip
self.api_token = api_token
self.api_url = f"http://{pihole_ip}/admin/api.php"
def get_blocked_queries(self, hours=24):
"""Get all blocked queries from last N hours"""
url = f"{self.api_url}?getAllQueries&auth={self.api_token}"
response = requests.get(url)
data = response.json()
blocked = []
cutoff = datetime.now() - timedelta(hours=hours)
for query in data.get('data', []):
if query[4] in ['2', '3']: # Blocked statuses
timestamp = datetime.fromtimestamp(query[0])
if timestamp > cutoff:
blocked.append({
'domain': query[2],
'client': query[3],
'timestamp': timestamp
})
return blocked
def suggest_allowlist(self, blocked_queries, min_frequency=10):
"""Suggest domains that might need allowlisting"""
# Count blocked domains
domain_counts = Counter(q['domain'] for q in blocked_queries)
suggestions = []
for domain, count in domain_counts.most_common(50):
# Check if domain is frequently blocked
if count >= min_frequency:
# Check if it's from multiple clients
clients = set(q['client'] for q in blocked_queries if q['domain'] == domain)
if len(clients) > 1: # Blocked by multiple devices
suggestion = {
'domain': domain,
'block_count': count,
'affected_clients': len(clients),
'reason': 'Frequently blocked by multiple clients',
'priority': 'HIGH' if count > 50 else 'MEDIUM'
}
suggestions.append(suggestion)
return suggestions
def check_known_services(self, domain):
"""Check if domain belongs to known legitimate services"""
known_services = {
'microsoft.com': 'Microsoft Services',
'msftncsi.com': 'Windows Network Connectivity Check',
'apple.com': 'Apple Services',
'icloud.com': 'iCloud',
'amazon.com': 'Amazon Services',
'amazonaws.com': 'AWS',
'googlevideo.com': 'YouTube',
'gvt1.com': 'Google Services',
'cloudfront.net': 'CloudFront CDN'
}
for service_domain, service_name in known_services.items():
if service_domain in domain:
return True, service_name
return False, None
def auto_allowlist(self, domain, dry_run=True):
"""Automatically add domain to allowlist"""
if dry_run:
print(f"[DRY RUN] Would allowlist: {domain}")
return
url = f"{self.api_url}?list=white&add={domain}&auth={self.api_token}"
response = requests.get(url)
if response.status_code == 200:
print(f"✓ Added to allowlist: {domain}")
else:
print(f"✗ Failed to allowlist: {domain}")
def generate_report(self):
"""Generate allowlist recommendation report"""
print("Analyzing blocked queries...")
blocked = self.get_blocked_queries(hours=24)
print(f"\nTotal blocked queries in last 24h: {len(blocked)}")
suggestions = self.suggest_allowlist(blocked)
print(f"\n=== Allowlist Recommendations ===\n")
for i, suggestion in enumerate(suggestions, 1):
print(f"{i}. {suggestion['domain']}")
print(f" Priority: {suggestion['priority']}")
print(f" Blocked: {suggestion['block_count']} times")
print(f" Clients affected: {suggestion['affected_clients']}")
# Check if it's a known service
is_known, service_name = self.check_known_services(suggestion['domain'])
if is_known:
print(f" ℹ️ Known service: {service_name}")
print(f" ✓ SAFE TO ALLOWLIST")
print()
return suggestions
def main():
PIHOLE_IP = "192.168.1.100"
API_TOKEN = "YOUR_API_TOKEN_HERE"
manager = AutoAllowlist(PIHOLE_IP, API_TOKEN)
suggestions = manager.generate_report()
# Optionally auto-allowlist known services
print("\n=== Auto-Allowlisting Known Services ===\n")
for suggestion in suggestions:
is_known, service_name = manager.check_known_services(suggestion['domain'])
if is_known and suggestion['block_count'] > 20:
manager.auto_allowlist(suggestion['domain'], dry_run=False)
if __name__ == "__main__":
main() Integration with Home Assistant
Automate Pi-hole management through Home Assistant:
Add to configuration.yaml:
# Pi-hole Integration
rest:
- resource: "http://192.168.1.100/admin/api.php?summaryRaw&auth=YOUR_TOKEN"
scan_interval: 60
sensor:
- name: "Pi-hole Total Queries"
value_template: "[[ value_json.dns_queries_today ]]"
unit_of_measurement: "queries"
- name: "Pi-hole Blocked Queries"
value_template: "[[ value_json.ads_blocked_today ]]"
unit_of_measurement: "queries"
- name: "Pi-hole Block Percentage"
value_template: "[[ value_json.ads_percentage_today | round(1) ]]"
unit_of_measurement: "%"
- name: "Pi-hole Unique Clients"
value_template: "[[ value_json.unique_clients ]]"
- name: "Pi-hole Status"
value_template: "[[ value_json.status ]]"
# Automation: Alert if blocking fails
automation:
- alias: "Pi-hole Blocking Alert"
trigger:
- platform: numeric_state
entity_id: sensor.pihole_block_percentage
below: 5
for:
minutes: 10
action:
- service: notify.mobile_app
data:
title: "Pi-hole Alert"
message: "Pi-hole blocking percentage is unusually low ([[ states('sensor.pihole_block_percentage') ]]%). Check configuration."
- alias: "Pi-hole High Query Load"
trigger:
- platform: numeric_state
entity_id: sensor.pihole_total_queries
above: 100000
action:
- service: notify.mobile_app
data:
title: "Pi-hole Performance"
message: "Pi-hole has processed [[ states('sensor.pihole_total_queries') ]] queries today. Monitor performance."
# Switch to enable/disable Pi-hole
switch:
- platform: rest
resource: "http://192.168.1.100/admin/api.php"
name: "Pi-hole"
body_on: "auth=YOUR_TOKEN&enable"
body_off: "auth=YOUR_TOKEN&disable=300"
is_on_template: "[[ value_json.status == 'enabled' ]]" Troubleshooting
Common Issues and Solutions
Issue: Websites Not Loading
Symptoms:
- Specific websites fail to load
- “Server not found” errors
- Partial page rendering (layout loads but images/videos don’t)
Diagnosis:
- Check Query Log:
Admin Panel → Tools → Query Log - Search for the problematic domain
- Note if it shows as “Blocked” (red)
- Identify which blocklist is blocking it
Solutions:
Temporary disable Pi-hole:
pihole disable 5m # Disable for 5 minutes If site works with Pi-hole disabled, it’s a false positive.
Add to allowlist:
Admin Panel → Whitelist → Add domain Common domains to allowlist:
# Microsoft services
www.msftncsi.com
*.microsoft.com
# Amazon/AWS
*.amazon.com
*.amazonaws.com
# Google services
*.googlevideo.com
*.gstatic.com
# Apple
*.apple.com
*.icloud.com Issue: Pi-hole Not Blocking Ads
Symptoms:
- Ads still visible in browsers
- Low block percentage (<5%)
- Dashboard shows few blocked queries
Diagnosis:
Check if devices are using Pi-hole:
# Windows
nslookup pi.hole
# Linux/Mac
dig pi.hole
# Should show Pi-hole's IP address Check router DHCP settings:
- Verify DNS is set to Pi-hole IP
- Ensure no secondary DNS bypasses Pi-hole
Solutions:
Force DNS on router:
- Router admin → DHCP settings
- Primary DNS: Pi-hole IP
- Secondary DNS: Leave blank (or use another Pi-hole)
- Save and reboot router
Flush DNS cache on clients:
# Windows
ipconfig /flushdns
ipconfig /release
ipconfig /renew
# Linux
sudo systemd-resolve --flush-caches
# macOS
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder Add more blocklists:
- Ensure you have at least 3-5 quality blocklists
- Run “Update Gravity” after adding lists
Issue: Slow DNS Resolution
Symptoms:
- Websites take long to start loading
- Frequent timeouts
- High query response times
Diagnosis:
Check upstream DNS performance:
# Test upstream DNS
dig @1.1.1.1 google.com
dig @8.8.8.8 google.com
# Compare to Pi-hole
dig @192.168.1.100 google.com Check Pi-hole resource usage:
# Docker
docker stats pihole
# Bare metal
htop # Look for pihole-FTL process Solutions:
Change upstream DNS:
Settings → DNS → Upstream DNS Servers Try different providers:
- Cloudflare: 1.1.1.1, 1.0.0.1
- Google: 8.8.8.8, 8.8.4.4
- Quad9: 9.9.9.9
- OpenDNS: 208.67.222.222, 208.67.220.220
Reduce blocklists:
- Remove slowest-updating lists
- Keep only 5-10 high-quality lists
Increase cache size:
sudo nano /etc/dnsmasq.d/05-pihole-custom.conf Add:
cache-size=20000 Restart:
pihole restartdns Issue: Can’t Access Web Interface
Symptoms:
- Can’t reach http://pi.hole/admin
- Connection refused or timeout
- 404 errors
Diagnosis:
Check if service is running:
# Docker
docker ps | grep pihole
# Bare metal
sudo systemctl status lighttpd
sudo systemctl status pihole-FTL Check port 80:
sudo netstat -tulpn | grep :80 Solutions:
Restart services:
# Docker
docker restart pihole
# Bare metal
sudo systemctl restart lighttpd
sudo systemctl restart pihole-FTL Access by IP:
http://192.168.1.100/admin Check firewall:
# Ubuntu/Debian
sudo ufw status
sudo ufw allow 80/tcp
# CentOS/RHEL
sudo firewall-cmd --add-port=80/tcp --permanent
sudo firewall-cmd --reload Issue: Pi-hole Service Crashes
Symptoms:
- DNS resolution fails intermittently
- Service restarts frequently
- High CPU or memory usage
Diagnosis:
Check logs:
# Docker
docker logs pihole --tail 100
# Bare metal
sudo journalctl -u pihole-FTL -n 100 Check system resources:
free -h # Memory
df -h # Disk space
top # CPU usage Solutions:
Increase memory:
- For Docker: Update docker-compose.yml with memory limits
- For VM: Allocate more RAM
- For bare metal: Add swap space
# Add 2GB swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile Reduce blocklists:
- Remove lists you don’t need
- Consolidate similar lists
Clean database:
pihole -g # Rebuild gravity
pihole flush # Clear query logs Issue: DHCP Conflicts
Symptoms:
- Devices can’t get IP addresses
- “169.254.x.x” addresses assigned
- Network intermittently unavailable
Diagnosis:
Check for multiple DHCP servers:
# On a client
sudo nmap --script broadcast-dhcp-discover Solutions:
Disable router DHCP:
- Access router admin interface
- Find DHCP settings
- Disable DHCP server
- Save and reboot router
Disable Pi-hole DHCP temporarily:
Settings → DHCP → Uncheck "DHCP server enabled" Use router DHCP + Pi-hole DNS:
- Keep router DHCP enabled
- Configure router to push Pi-hole IP as DNS
- Disable Pi-hole DHCP
Emergency Recovery
If Pi-hole is completely broken:
Option 1: Restore from backup
Settings → Teleporter → Restore Upload your backup file and restart services.
Option 2: Use public DNS temporarily
On router:
- Primary DNS: 1.1.1.1
- Secondary DNS: 8.8.8.8
This bypasses Pi-hole while you troubleshoot.
Option 3: Reinstall
# Docker
docker-compose down
docker volume rm pihole_etc-pihole
docker-compose up -d
# Bare metal
pihole uninstall
curl -sSL https://install.pi-hole.net | bash Integration with Homelab Services
Pi-hole + Unbound (Recursive DNS)
For maximum privacy, use Unbound to resolve DNS queries recursively without relying on third-party DNS providers.
Architecture
Client Device
↓
Pi-hole (DNS Sinkhole + Ad Blocking)
↓
Unbound (Recursive DNS Resolver)
↓
Root DNS Servers (authoritative) See the Docker deployment section for Unbound integration.
Pi-hole + Nginx Proxy Manager
Access Pi-hole web interface via custom domain with HTTPS:
Nginx Proxy Manager Configuration:
Add Proxy Host:
- Domain:
pihole.yourdomain.com - Scheme:
http - Forward Hostname:
192.168.1.100 - Forward Port:
80
- Domain:
Enable SSL:
- Request Let’s Encrypt certificate
- Force SSL
Add custom Pi-hole DNS record:
Local DNS → DNS Records pihole.yourdomain.com → 192.168.1.100
Pi-hole + AdGuard Home (Redundancy)
Run both for failover:
Router Configuration:
- Primary DNS: Pi-hole (192.168.1.100)
- Secondary DNS: AdGuard Home (192.168.1.101)
If Pi-hole fails, AdGuard Home provides continued ad blocking.
Pi-hole + VPN (Wireguard/OpenVPN)
Access Pi-hole DNS while away from home:
Wireguard Server Configuration:
Add to wg0.conf:
[Interface]
Address = 10.0.0.1/24
DNS = 192.168.1.100 # Pi-hole IP
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT Clients connected to VPN will use Pi-hole for DNS.
Best Practices
Security Hardening
Change Default Password:
pihole -a -p Use a strong password (16+ characters, mixed case, numbers, symbols).
Enable HTTPS:
sudo apt install certbot
sudo certbot --apache -d pihole.yourdomain.com Restrict Web Interface Access:
Edit lighttpd configuration:
sudo nano /etc/lighttpd/lighttpd.conf Add IP restriction:
$HTTP["remoteip"] !~ "192.168.1.0/24" {
url.access-deny = ( "" )
} Disable Query Logging for Privacy:
Settings → Privacy → Privacy Level → Anonymous mode Backup Strategy
Automated Backups:
Create backup script:
#!/bin/bash
# backup-pihole.sh
BACKUP_DIR="/path/to/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# For Docker
docker exec pihole pihole -a -t
cp ./etc-pihole/pi-hole-*.tar.gz "$BACKUP_DIR/pihole-backup-$DATE.tar.gz"
# Keep only last 7 backups
find "$BACKUP_DIR" -name "pihole-backup-*.tar.gz" -mtime +7 -delete Schedule with cron:
crontab -e Add:
0 2 * * 0 /path/to/backup-pihole.sh Monitoring and Alerts
Email Alerts for Issues:
Create monitoring script:
#!/bin/bash
# check-pihole.sh
PIHOLE_IP="192.168.1.100"
EMAIL="[email protected]"
# Check if Pi-hole is responding
if ! curl -s "http://$PIHOLE_IP/admin/api.php" > /dev/null; then
echo "Pi-hole is not responding!" | mail -s "ALERT: Pi-hole Down" "$EMAIL"
fi
# Check block percentage
BLOCK_PCT=$(curl -s "http://$PIHOLE_IP/admin/api.php?summaryRaw" | jq -r '.ads_percentage_today')
if (( $(echo "$BLOCK_PCT < 5" | bc -l) )); then
echo "Pi-hole blocking percentage is unusually low: $BLOCK_PCT%" | mail -s "ALERT: Pi-hole Low Blocking" "$EMAIL"
fi Maintenance Schedule
Weekly:
- Review query log for issues
- Check for new blocklist updates
- Review top blocked domains
Monthly:
- Update Pi-hole:
pihole -upordocker-compose pull && docker-compose up -d - Review and optimize blocklists
- Check system resources
- Test backup restoration
Quarterly:
- Review and update allowlist
- Evaluate new blocklists
- Update documentation
- Test failover procedures
Conclusion
Pi-hole is an essential component of a privacy-focused homelab, providing network-wide ad blocking, DNS management, and valuable insights into network traffic. Through this guide, you’ve learned how to deploy Pi-hole using multiple methods, configure advanced features, optimize performance, and integrate AI-powered automation for intelligent threat detection and management.
Key Takeaways
- DNS-level blocking is powerful: One Pi-hole protects all devices on your network without per-device configuration
- Multiple deployment options: Choose Docker for ease of use, bare metal for performance, or VMs for isolation
- Quality over quantity: 5-10 well-maintained blocklists outperform 50 random lists
- Integration is key: Pi-hole works best when integrated with monitoring, automation, and other homelab services
- Automation enhances value: AI-powered scripts can detect threats, optimize blocklists, and reduce maintenance
Next Steps
Immediate Actions:
- Deploy your chosen Pi-hole configuration
- Configure network devices to use Pi-hole
- Add 3-5 quality blocklists
- Create your first configuration backup
Short-Term Improvements:
- Set up Unbound for recursive DNS
- Implement automated monitoring
- Integrate with Home Assistant or monitoring stack
- Create allowlist for false positives
Long-Term Goals:
- Deploy redundant Pi-hole instance for failover
- Implement AI-powered threat detection
- Build comprehensive monitoring dashboards
- Create automated maintenance workflows
Additional Resources
- Official Pi-hole Documentation: https://docs.pi-hole.net/
- Pi-hole GitHub: https://github.com/pi-hole/pi-hole
- Community Forum: https://discourse.pi-hole.net/
- Firebog Blocklists: https://firebog.net/
- Reddit Community: r/pihole
Related Guides
Explore these complementary guides on Homelabbing.io:
- Unbound DNS Resolver: Maximum privacy with recursive DNS
- Nginx Proxy Manager: Secure access to homelab services
- Docker Networking: Advanced container configurations
- Prometheus & Grafana: Comprehensive monitoring solutions
- Home Assistant: Smart home automation and integration
Document Version: 1.0 Last Updated: December 16, 2025 Difficulty: Beginner Estimated Completion Time: 2-3 hours Maintenance Required: 15 minutes per week
Quick Reference
Essential Commands
# Docker
docker-compose up -d # Start Pi-hole
docker-compose down # Stop Pi-hole
docker-compose logs -f pihole # View logs
docker restart pihole # Restart
docker exec pihole pihole -g # Update gravity
# Bare Metal
pihole status # Check status
pihole -up # Update Pi-hole
pihole -g # Update gravity
pihole -a -p # Change password
pihole -t # Tail query log
pihole restartdns # Restart DNS
pihole -w domain.com # Whitelist domain
pihole -b domain.com # Blacklist domain
# Network Testing
nslookup pi.hole # Test local DNS
nslookup google.com 192.168.1.100 # Test Pi-hole resolution
dig @192.168.1.100 example.com # Detailed DNS query
ping 1.1.1.1 # Test connectivity Configuration Files
| Location | Purpose |
|---|---|
/etc/pihole/ | Main configuration |
/etc/dnsmasq.d/ | DNS configuration |
/var/log/pihole/ | Log files |
/etc/pihole/pihole-FTL.db | Gravity database |
./etc-pihole/ | Docker volume |
Default Ports
| Port | Protocol | Purpose |
|---|---|---|
| 53 | TCP/UDP | DNS |
| 80 | TCP | Web interface (HTTP) |
| 443 | TCP | Web interface (HTTPS) |
| 67 | UDP | DHCP (if enabled) |
| 4711 | TCP | FTL API |
Happy homelabbing! If you found this guide helpful, consider contributing improvements or sharing your Pi-hole setup in the community forums.