Overview
Pi-hole is a network-level advertisement and internet tracker blocking application. When integrated with WireGuard Easy, it provides:- Network-wide ad blocking without client-side software
- Protection against tracking and malicious domains
- Detailed DNS query analytics and statistics
- Custom DNS filtering with whitelist/blacklist support
- Local DNS records for home network devices
- DHCP server capabilities (optional)
Prerequisites
Before starting, ensure you have:- A working WireGuard Easy installation
- Docker and Docker Compose installed
- Basic understanding of Docker networking
- At least 2GB RAM on your host system
- (Optional) A reverse proxy for secure web access
Architecture
The integration architecture consists of:- WireGuard Easy: VPN server managing client connections
- Pi-hole: DNS server providing ad-blocking and filtering
- Shared Docker Network: Both containers communicate via a bridge network
- DNS Redirection: iptables rules force all DNS traffic through Pi-hole
- Upstream DNS: Pi-hole forwards clean queries to upstream resolvers
Installation Steps
sudo mkdir -p /etc/docker/containers/pihole
sudo mkdir -p /etc/docker/volumes/pihole/etc-pihole
sudo mkdir -p /etc/docker/volumes/pihole/etc-dnsmasq.d
If the network already exists from a previous setup, you can skip this step or remove the existing network first with
sudo docker network rm wg.services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
hostname: pihole
environment:
# Timezone - adjust to your location
TZ: 'America/New_York'
# Web interface password
WEBPASSWORD: 'your-secure-password'
# DNS settings
PIHOLE_DNS_: '1.1.1.1;8.8.8.8'
# Web interface settings
VIRTUAL_HOST: 'pihole.local'
# IPv6 support
IPv6: 'true'
# Interface listening behavior
DNSMASQ_LISTENING: 'all'
# Web server port (if using reverse proxy)
WEB_PORT: '80'
volumes:
- /etc/docker/volumes/pihole/etc-pihole:/etc/pihole
- /etc/docker/volumes/pihole/etc-dnsmasq.d:/etc/dnsmasq.d
# Cap drop for security
cap_add:
- NET_ADMIN
networks:
wg:
ipv4_address: 10.42.42.53
ipv6_address: fdcc:ad94:bacf:61a3::53
networks:
wg:
external: true
Port Configuration: This configuration doesn’t expose ports to the host. Pi-hole is only accessible via the Docker network and optionally through a reverse proxy. If you need direct host access, add:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: wg-easy
restart: unless-stopped
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
volumes:
- /etc/docker/volumes/wg-easy:/etc/wireguard
networks:
wg:
ipv4_address: 10.42.42.2
ipv6_address: fdcc:ad94:bacf:61a3::2
environment:
# Basic Configuration
- WG_HOST=vpn.example.com
- PASSWORD=your-admin-password
# DNS Configuration - Point to Pi-hole
- WG_DEFAULT_DNS=10.42.42.53,fdcc:ad94:bacf:61a3::53
# Network Configuration
- WG_ALLOWED_IPS=0.0.0.0/0,::/0
- WG_PORT=51820
# Client Configuration
- WG_PERSISTENT_KEEPALIVE=25
- WG_MTU=1420
networks:
wg:
external: true
iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.53; iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.53; ip6tables -t nat -A PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::53; ip6tables -t nat -A PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::53; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE;
iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT || true; iptables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination 10.42.42.53 || true; iptables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination 10.42.42.53 || true; ip6tables -t nat -D PREROUTING -i wg0 -p udp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::53 || true; ip6tables -t nat -D PREROUTING -i wg0 -p tcp --dport 53 -j DNAT --to-destination fdcc:ad94:bacf:61a3::53 || true; iptables -D FORWARD -i wg0 -j ACCEPT || true; iptables -D FORWARD -o wg0 -j ACCEPT || true; ip6tables -D FORWARD -i wg0 -j ACCEPT || true; ip6tables -D FORWARD -o wg0 -j ACCEPT || true; iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE || true; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE || true;
DNS Leak Prevention: These hooks ensure that ALL DNS traffic from VPN clients is routed through Pi-hole, preventing DNS leaks even if clients try to use alternative DNS servers.
# Start Pi-hole
cd /etc/docker/containers/pihole
sudo docker compose up -d
# Wait for Pi-hole to fully initialize (about 30 seconds)
sleep 30
# Restart WireGuard Easy to apply changes
cd /etc/docker/containers/wg-easy
sudo docker compose down
sudo docker compose up -d
# Verify both containers are running
sudo docker ps | grep -E 'pihole|wg-easy'
http://<your-server-ip>:8080/admin (if you exposed the port)http://10.42.42.53/adminWEBPASSWORD- Select your preferred upstream DNS servers
- Enable “Use DNSSEC”
- Consider enabling “Use Conditional Forwarding” for local network
- Default lists are enabled
- Add additional blocklists if desired
- Click “Update Gravity” to apply
- Review and adjust query logging settings
- Set API/Web interface settings
Network Configuration Details
IP Addressing Scheme
| Service | IPv4 Address | IPv6 Address | Purpose |
|---|---|---|---|
| WireGuard Easy | 10.42.42.2 | fdcc:ad94:bacf:61a3::2 | VPN gateway |
| Pi-hole | 10.42.42.53 | fdcc:ad94:bacf:61a3::53 | DNS server |
| Docker Network | 10.42.42.0/24 | fdcc:ad94:bacf:61a3::/64 | Container subnet |
The Pi-hole IP address ends in
.53 (the DNS port number) for easy memorization, but you can use any available address in the subnet.DNS Query Flow
- Client Request: VPN client sends DNS query
- Traffic Interception: iptables DNAT rule intercepts port 53 traffic
- Pi-hole Processing: Query checked against blocklists
- Blocked Domains: Return NXDOMAIN or custom block page
- Allowed Domains: Forward to upstream DNS servers
- Response: Answer returned to client through reverse path
Verification
Test ad-blocking functionality
From a connected VPN client:Verify DNS traffic in Pi-hole
- Open Pi-hole admin interface
- Navigate to Dashboard
-
You should see:
- Queries from VPN client IPs (10.8.0.x range)
- Blocked queries count increasing
- Top blocked domains list
-
Go to Tools → Query Log:
- See real-time DNS queries
- Verify queries are coming from VPN clients
- Check that ads are being blocked (red status)
Check container connectivity
Verify DNS leak protection
From VPN client, use online tools:- Visit https://dnsleaktest.com
- Run extended test
- All DNS queries should go through your Pi-hole server’s ISP
- No third-party DNS servers should be detected
Optimization
Pi-hole performance tuning
Create a custom dnsmasq configuration: File:/etc/docker/volumes/pihole/etc-dnsmasq.d/99-custom.conf
System-level optimizations
Increase file descriptors and connection limits: File:/etc/sysctl.d/99-pihole.conf
Blocklist optimization
Recommended blocklists (add in Pi-hole → Settings → Blocklists):Reverse Proxy Integration
With Traefik
Add Traefik labels to your Pi-hole service:With Caddy
Add to your Caddyfile:With Nginx
Create an Nginx configuration:Troubleshooting
Ads not being blocked
Symptom: Connected to VPN but ads still appear. Diagnosis steps:-
Check if Pi-hole is receiving queries:
-
Verify iptables rules:
-
Test DNS from WireGuard container:
-
Update gravity database:
-
Check blocklists are enabled:
- Open Pi-hole admin → Settings → Blocklists
- Ensure lists are checked and updated recently
-
Verify client DNS settings:
- Download WireGuard config from wg-easy
- Check
DNS = 10.42.42.53, fdcc:ad94:bacf:61a3::53
-
Flush DNS cache on client:
- Windows:
ipconfig /flushdns - macOS:
sudo dscacheutil -flushcache - Linux:
sudo systemd-resolve --flush-caches
- Windows:
Pi-hole web interface not accessible
Symptom: Cannot access Pi-hole admin panel. Solutions:-
Check container status:
-
Verify password is set:
-
Check lighttpd is running:
-
Restart Pi-hole:
-
Check firewall rules (if accessing from host):
DNS resolution extremely slow
Symptom: Web pages take long time to load, DNS timeouts. Diagnosis:-
Reduce blocklist count:
- Too many blocklists can slow down Pi-hole
- Aim for 500K-1M blocked domains max
- Remove duplicate or overlapping lists
-
Change upstream DNS servers:
- Increase cache size (see Optimization section)
-
Check disk I/O:
-
Allocate more resources to Docker container:
VPN clients lose internet connectivity
Symptom: Connected to VPN but can’t browse websites. Diagnosis:-
Verify IP forwarding:
-
Check MASQUERADE rules:
-
Restart both services:
-
Check Pi-hole is responding:
Pi-hole database corruption
Symptom: Pi-hole crashes, query log shows errors, gravity update fails. Solutions:-
Check database integrity:
-
Repair database:
-
Rebuild gravity database:
-
Full reset (last resort):
IPv6 issues
Symptom: IPv6 websites don’t load or DNS over IPv6 fails. Solutions:-
Enable IPv6 in Pi-hole:
- Settings → System → Enable IPv6
- Restart DNS resolver
-
Check IPv6 forwarding:
-
Verify IPv6 upstream DNS:
-
Test IPv6 connectivity:
High memory usage
Symptom: Pi-hole container consuming excessive RAM. Solutions:-
Check memory usage:
-
Reduce cache size:
-
Disable query logging temporarily:
-
Clear old logs:
-
Set memory limit in docker-compose:
Advanced Configuration
Local DNS records
Add custom DNS entries for local services: Method 1: Via Web Interface- Go to Local DNS → DNS Records
- Add entries:
- Domain:
server.local - IP Address:
192.168.1.100
- Domain:
CNAME records
Create CNAME records for multiple domain names: File:/etc/docker/volumes/pihole/etc-dnsmasq.d/05-cnames.conf
Conditional forwarding
Forward local domain queries to your router:- Go to Settings → DNS → Advanced DNS settings
- Enable “Conditional forwarding”
- Set:
- Local network in CIDR:
192.168.1.0/24 - IP address of DHCP server (router):
192.168.1.1 - Local domain name:
home.local
- Local network in CIDR:
Whitelist/Blacklist management
Whitelist a domain (allow it to resolve):Group management
Assign different clients to different filtering groups:-
Create groups:
- Go to Group Management → Groups
- Add groups: “Kids”, “Adults”, “IoT”
-
Assign clients to groups:
- Go to Group Management → Clients
- Add client IP addresses from VPN subnet
- Assign to appropriate groups
-
Configure group-specific blocklists:
- Go to Group Management → Adlists
- Assign blocklists to specific groups
API integration
Pi-hole provides a RESTful API for automation: Get statistics:Complete Docker Compose Example
Comprehensive example with WireGuard Easy, Pi-hole, and Traefik:Security Best Practices
-
Strong passwords:
- Use unique, complex passwords for both Pi-hole and WireGuard
- Consider using a password manager
-
Regular updates:
-
Query logging privacy:
- Consider disabling query logging for privacy
- Or set short retention periods
- Settings → Privacy → Set to “Anonymous mode”
-
HTTPS for admin interface:
- Always use reverse proxy with TLS
- Never expose Pi-hole directly to internet
-
Rate limiting:
- Prevent DNS amplification attacks
- Configure in dnsmasq:
dns-ratelimit=1000,10
-
Firewall rules:
Monitoring and Maintenance
Query statistics
View real-time statistics:Health checks
Create a health check script: File:/usr/local/bin/check-pihole.sh
Backup and restore
Backup Pi-hole configuration:Log rotation
Pi-hole automatically rotates logs, but you can configure it: File:/etc/docker/volumes/pihole/etc-dnsmasq.d/06-logs.conf
Update automation
Automate updates with a script:Migration from Other DNS Solutions
From AdGuard Home
- Export AdGuard Home configuration
- Manually recreate custom DNS entries in Pi-hole
- Import blocklists (most are compatible)
- Update WireGuard DNS settings
- Test thoroughly before decommissioning AdGuard
From Unbound
- Note all custom DNS zones
- Configure Pi-hole with same upstream resolvers
- Recreate local zones in Pi-hole
- Update WireGuard to use Pi-hole
- Optionally: Use Pi-hole + Unbound together