Every device on your network sends DNS queries — dozens per minute, even when idle. Each query is a privacy leak. Your ISP, Google, Cloudflare, or whoever runs your upstream DNS resolver logs every domain your devices reach for.
Running Pi-hole with Unbound as a recursive DNS resolver gives you two things: network-wide ad blocking and complete DNS privacy. Pi-hole filters out tracking and ad domains at the DNS level. Unbound resolves queries recursively from the root servers — no upstream provider sees your traffic.
This guide covers a production-grade Docker Compose deployment of Pi-hole v6 and Unbound, local DNS records for homelab services, integration with a MikroTik router, monitoring, and query analysis. Everything runs in two containers with persistent configuration and log rotation.
Why Recursive DNS Changes the Privacy Game
Most DNS setups use a forwarding resolver — Pi-hole receives a query and forwards it to 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), or your ISP’s resolver. That upstream provider knows every site you visit. They may log, monetize, or share that data.
A recursive resolver like Unbound starts at the DNS root servers and follows the delegation chain: root → TLD → authoritative nameserver. The query is resolved entirely on your hardware. No third party ever sees the full chain. The only entity that knows which domains you queried is you.
The latency cost is small — approximately 10-30ms for the initial query (cache miss), then zero for subsequent queries served from the local cache. Unbound caches aggressively by default, so repeat queries to the same domains incur no additional recursion.
What you gain with the Pi-hole + Unbound stack:
- Ad and tracker blocking — Pi-hole’s blocklists strip ads from every device, including smart TVs and IoT gadgets that can’t run ad blockers
- DNS privacy — no upstream logging of your queries. Unbound validates DNSSEC automatically
- Local DNS — resolve homelab services by hostname without external DNS providers or split-brain setups
- Query insights — see exactly which devices talk to which domains, and block suspicious outbound traffic
Docker Compose — Pi-hole v6 and Unbound
This stack runs two containers sharing the same network namespace.
Unbound runs inside Pi-hole’s network stack and listens on
127.0.0.1:5335. Pi-hole forwards queries to it as the only upstream
DNS server.
Directory preparation:
|
|
Docker Compose configuration:
|
|
Key configuration notes:
PIHOLE_DNS_: 127.0.0.1#5335— tells Pi-hole to use Unbound on localhost port 5335 as the single upstream. The trailing underscore is intentional (Pi-hole v6 parses this env var).DNSSEC: "true"— enables DNSSEC validation through Unbound. Unbound does the cryptographic verification; Pi-hole displays the results.FTLCONF_REPLY_ADDR4— Pi-hole v6 needs to know its own IP to respond correctly. Set this to the host IP where Pi-hole is bound.network_mode: service:pihole— Unbound shares Pi-hole’s network stack, so localhost inside the container reaches both. No need to expose Unbound on any port.- The web interface is mapped to port 8081 to avoid conflicts with other services on port 80.
Deploy:
|
|
Unbound Configuration — Recursive Resolution
Unbound needs a config file that enables recursion and DNSSEC, and binds to port 5335 instead of the default 53 (Pi-hole owns port 53).
Create /srv/docker/unbound/unbound.conf:
|
|
Set up root hints for bootstrap:
|
|
The do-not-query-localhost: no directive is critical — without it,
Unbound refuses to answer queries on localhost. The forward-zone
block is intentionally commented out or empty: Unbound should resolve
recursively, not forward to another resolver.
Verify Unbound is working:
|
|
If queries fail, check the Unbound logs:
|
|
Local DNS Records for Homelab Services
One of the most useful Pi-hole features for the homelab is local DNS records. Instead of remembering IPs or relying on external DNS for internal services, add them to Pi-hole’s local DNS configuration.
Access the Pi-hole container and add records:
|
|
Or use the web UI:
- Login to
http://10.0.20.10:8081/admin - Local DNS → DNS Records
- Add each domain → IP mapping
Once configured, every device on your network resolves
srv1.gntech.internal and proxmox.gntech.internal to the correct IP —
no hosts files, no mDNS, no external DNS providers. This also works for
containers on the same Docker host that need to reach services by
hostname.
Local DNS best practices:
- Use a
.internalor.home.arpaTLD — never use.local(mDNS conflict) or unregistered public TLDs - Add CNAME records for common aliases (e.g.,
srv1 → proxmox) - Keep records in sync with your actual IP assignments — document them in your homelab README
- Test resolution from a client:
nslookup pihole.gntech.internal 10.0.20.10
MikroTik Router Integration
Pointing your MikroTik router at Pi-hole makes the entire network use your filtered, private DNS — including devices that hardcode their own DNS settings (most IoT devices use the router’s DHCP-provided DNS).
Set Pi-hole as the system DNS on RouterOS:
|
|
Or through WinBox/WebFig:
- IP → DNS
- Set Servers:
10.0.20.10 - Uncheck “Allow Remote Requests” (you don’t want your router acting as an open resolver)
Force all devices to use Pi-hole via DHCP:
|
|
Redirect all DNS traffic (even hardcoded DoH/DoT):
Some devices (Chromecast, Samsung TVs, certain IoT) use hardcoded DNS like 8.8.8.8 or DNS-over-HTTPS. MikroTik can hijack those:
|
|
This NAT rule intercepts every DNS packet regardless of the destination server and redirects it to Pi-hole. Devices using hardcoded 8.8.8.8 still get filtered through your blocklists.
Block DNS-over-HTTPS (DoH) at the firewall level:
|
|
Monitor Pi-hole client activity from MikroTik:
Enable conditional forwarding or simply check Pi-hole’s query log at
http://10.0.20.10:8081/admin/querylog.php — every query shows the
client IP, so you can identify which device generated it.
Blocklists and Query Tuning
Pi-hole ships with a default blocklist, but for a homelab, you want more aggressive blocking without breaking legitimate services.
Recommended blocklist strategy:
|
|
Update gravity (blocklist database):
|
|
Common whitelist entries for homelab services:
Some legit services get caught in aggressive blocklists. Whitelist them as needed from the Pi-hole web UI (Group Management → Domain Whitelist):
api.github.com— Git operations and CI/CDalerts.snyk.io— vulnerability scanningdc.services.visualstudio.com— VS Code telemetry (only if you use it)device-messaging.owox.com— monitoring toolsfirebase-settings.crashlytics.com— crash reportinggraph.facebook.com— if you use Facebook/Instagram on this networkpush.services.mozilla.com— Firefox push notificationsdownload.docker.com— Docker pulls
Monitor the query log for the first week after deployment. Whitelist what breaks, add lists that catch more trackers. The sweet spot blocks 20-30% of queries on a typical homelab network without breaking anything useful.
Monitoring and Query Analysis
Pi-hole’s web interface provides real-time and historical query data. The dashboard shows:
- Total queries over time (daily, weekly, monthly)
- Blocked percentage — target 20-30% on a typical home network. Higher means aggressive lists breaking things; lower means you could add more lists
- Top blocked domains — identifies the worst offenders (often analytics, ad networks, telemetry)
- Top clients — sorted by query count. Surprising insight into which devices talk to the internet most
- Query log — real-time feed of every DNS query with client IP, domain, and action (allowed/blocked)
Grafana dashboard for Pi-hole metrics:
If you already run Grafana (as covered in the homelab monitoring stack), Pi-hole exposes Prometheus metrics on port 8081:
|
|
Or use the community Pi-hole exporter for richer metrics:
|
|
Then scrape 10.0.20.10:9617 in Prometheus and build a Grafana dashboard
for Pi-hole queries over time, blocked percentages, top clients, and DNS
response times.
Automation triggers using query patterns:
The query log can detect compromised devices. A sudden spike in queries to suspicious domains from a single client is a red flag. Set up alerting:
- Query rate alerts — if a client exceeds 1000 queries/hour, investigate. Could be a malware beacon.
- New domain probing — repeated NXDOMAIN responses to random subdomains (DNS tunneling indicator)
- Cryptominer domains — if the NoCoin list blocks a query from a device that shouldn’t be mining, that device is compromised
Updates, Backups, and Maintenance
Updating the containers:
|
|
Updating blocklists (automated):
Pi-hole’s gravity updates every 7 days by default. Run an immediate update after adding new lists:
|
|
Backup strategy:
Pi-hole’s data volume contains everything — blocklist configuration, local DNS records, client history, and query logs. Back it up regularly:
|
|
Restoring from backup:
|
|
Summary
|
|
Key takeaways:
- Pi-hole + Unbound is the privacy gold standard — no upstream DNS provider logs your queries. Every resolution starts from the root servers on your hardware.
- The network-mode trick avoids port conflicts — Unbound runs inside Pi-hole’s network stack and uses port 5335. Only Pi-hole’s port 53 is exposed.
- Force all DNS through Pi-hole with MikroTik NAT rules to catch devices with hardcoded DNS — IoT and smart TVs are the worst offenders.
- Local DNS records eliminate IP management — add
*.gntech.internalrecords in Pi-hole and never type an IP address again. - Monitor the query log — it’s the best network visibility tool in your homelab. Compromised devices, unexpected call-home behavior, and DNS tunneling all show up here first.
DNS is the backbone of every homelab. Running it yourself with ad blocking and recursive resolution isn’t just about privacy — it’s about network visibility, control, and understanding what every device on your network is actually doing.