Pi-hole + Unbound: Building a Privacy-Focused DNS Infrastructure
I’ve been running Pi-hole for years, but recently upgraded to Pi-hole v6 with Unbound as a recursive DNS resolver. The setup provides better privacy, faster queries, and more control over your DNS infrastructure. But getting it working wasn’t straightforward—I encountered several real problems that most guides don’t mention.
This article covers the actual setup process, the problems I encountered, and how I solved them. It’s based on real experience, not theoretical best practices.
Why Pi-hole + Unbound?
Most people use Pi-hole as an ad blocker, but it’s much more powerful when combined with Unbound. Here’s why this combination is worth the extra effort:
Privacy benefits
Unbound acts as a recursive DNS resolver. Instead of forwarding queries to Google DNS (8.8.8.8) or Cloudflare (1.1.1.1), it queries the root DNS servers directly. This means:
- No third-party DNS provider sees your queries
- No query logging by external services
- No DNS-based tracking or analytics
- Complete control over your DNS infrastructure
Performance improvements
Recursive DNS provides local caching benefits. Unbound caches responses locally, so frequently accessed domains get served instantly from cache. The initial setup takes a bit longer, but cached queries can be faster than repeated lookups to external DNS providers. However, uncached queries that require full recursive resolution might be slower than using well-peered public DNS resolvers due to network latency to root and TLD servers.
Control and customization
You own your DNS infrastructure. You can:
- Block domains at the DNS level (ad blocking)
- Create custom DNS records for internal services
- Implement DNS-based security policies
- Monitor and log DNS queries locally
The installation process: what actually works
I used an automated installation script that handles most of the complexity. The script installs Pi-hole v6 and configures Unbound as a recursive resolver on port 5335.
Prerequisites
- Raspberry Pi or similar device (I used Raspberry Pi 4B models, but any Pi or even other devices work)
- Raspberry Pi OS Lite (headless installation)
- Static IP address (configured in your router)
- SSH access enabled
Manual installation process
I used a custom installation script, but here’s what the manual process looks like:
Step 1: Install Pi-hole v6
# Install Pi-hole using the official installer
curl -sSL https://install.pi-hole.net | sudo bash
Step 2: Install and configure Unbound
# Install Unbound and required packages
sudo apt update
sudo apt install -y unbound dns-root-data
# Download root hints for DNS resolution
sudo wget -qO /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
# Generate root key for DNSSEC validation
sudo unbound-anchor -a /var/lib/unbound/root.key
# Set proper ownership
sudo chown unbound:unbound /var/lib/unbound/root.key
Step 3: Configure Unbound as recursive resolver
# Create Unbound configuration
sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf << EOF
server:
verbosity: 0
port: 5335
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
root-hints: "/var/lib/unbound/root.hints"
auto-trust-anchor-file: "/var/lib/unbound/root.key"
hide-version: yes
hide-identity: yes
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: yes
cache-min-ttl: 3600
cache-max-ttl: 86400
prefetch: yes
qname-minimisation: yes
aggressive-nsec: yes
EOF
# Enable and start Unbound
sudo systemctl enable unbound.service
sudo systemctl restart unbound.service
Manual configuration
After installation, you need to configure Pi-hole to use Unbound:
# Point Pi-hole to local Unbound
sudo pihole -a setdns 127.0.0.1#5335
# Configure Pi-hole to listen on all interfaces
sudo pihole-FTL --config dns.listeningMode ALL
# Restart DNS service
sudo pihole restartdns
TLS configuration: learning the hard way
Setting up HTTPS for the Pi-hole admin interface isn’t strictly necessary—the admin panel works fine over HTTP on your local network. But I wanted to learn how to configure TLS properly, and it turned out to be more complicated than expected. I also set up certdeploy for automated certificate distribution, but that’s a topic for another day.
Problem 1: Port configuration issues
Pi-hole FTL requires specific port configuration for HTTPS to work properly.
What went wrong: I initially configured Pi-hole to only listen on port 443 (HTTPS), but it failed to start properly.
The solution: For Pi-hole’s built-in web server, you need both HTTP (port 80) and HTTPS (port 443) configured for proper HTTPS operation:
# Check current port configuration
sudo pihole-FTL --config webserver.port
# Set correct configuration (HTTP redirect + HTTPS)
sudo pihole-FTL --config webserver.port "80r,443s"
# Restart FTL
sudo systemctl restart pihole-FTL
Why this matters: The 80r
component handles HTTP-to-HTTPS redirects, which is required for proper SSL initialization.
Problem 2: Certificate chain issues
Let’s Encrypt certificates require the full certificate chain to work properly.
What went wrong: I initially only copied the server certificate and private key, but clients couldn’t validate the certificate chain.
The solution: Include the full certificate chain:
# Copy full certificate chain (server + intermediate certificates)
sudo cat /etc/letsencrypt/live/yourdomain.com/fullchain.pem /etc/letsencrypt/live/yourdomain.com/privkey.pem | sudo tee /etc/pihole/tls.pem > /dev/null
# Restart FTL
sudo systemctl restart pihole-FTL
Why this matters: Browsers and clients need the intermediate certificates to validate the full certificate chain back to a trusted root CA.
Problem 3: SSH key mismatches
When setting up automated certificate distribution, SSH key authentication can be tricky.
What went wrong: I used RSA SSH keys initially, but the specific certificate distribution tool I was using preferred Ed25519 keys for security reasons.
The solution: Generate and configure Ed25519 SSH keys:
# Generate Ed25519 key pair
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_pihole
# Copy public key to Pi-hole servers
ssh-copy-id -i ~/.ssh/id_ed25519_pihole.pub vic@pi-hole-server
Why this matters: Modern security tools prefer Ed25519 keys over RSA, and some tools require specific key types.
DNS management: automating the process
Managing DNS records across multiple Pi-hole servers can be time-consuming. I automated this process using a Python script and nebula-sync.
Python import script
I created a Python script to bulk import DNS records from a CSV file:
#!/usr/bin/env python3
import csv
import subprocess
import sys
def import_hosts(csv_file):
with open(csv_file, 'r') as f:
reader = csv.reader(f)
for row in reader:
if len(row) >= 2:
ip, hostname = row[0], row[1]
# Add static DNS record
subprocess.run(['pihole', '-a', 'addstaticdns', ip, hostname])
if __name__ == "__main__":
import_hosts(sys.argv[1])
CSV format: ip,hostname,description
Example:
192.168.1.10,pihole1.lan,Primary Pi-hole
192.168.1.11,pihole2.lan,Secondary Pi-hole
192.168.1.20,nas.lan,Network Storage
Nebula-sync for replication
I use nebula-sync to automatically replicate configuration across multiple Pi-hole servers.
Setup process:
- Install nebula-sync on the master server
- Configure systemd timer to run daily at 3:00 AM
- Set up SSH keys for secure communication between servers
- Test replication manually before enabling automation
Configuration:
# Install nebula-sync
curl -sSL https://github.com/lovelaze/nebula-sync/releases/download/v0.11.0/nebula-sync_0.11.0_linux_arm64.tar.gz | tar -xz
sudo mv nebula-sync /usr/local/bin/
# Create systemd service
sudo tee /etc/systemd/system/nebula-sync.service << EOF
[Unit]
Description=Nebula-Sync Pi-hole configuration replication
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/nebula-sync run --env-file /etc/nebula-sync/nebula-sync.env
EOF
# Create systemd timer
sudo tee /etc/systemd/system/nebula-sync.timer << EOF
[Unit]
Description=Run Nebula-Sync every day at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Unit=nebula-sync.service
[Install]
WantedBy=timers.target
EOF
# Enable and start timer
sudo systemctl enable nebula-sync.timer
sudo systemctl start nebula-sync.timer
Real troubleshooting: lessons learned
Here are the actual problems I encountered and how I solved them:
Issue 1: Unbound service conflicts
Problem: Unbound failed to start because it was trying to bind to port 53, which Pi-hole was already using.
Solution: Configure Unbound to use port 5335 and ensure Pi-hole points to the correct port.
Issue 2: Corrupted trust anchor
Problem: After a system restart, DNS resolution failed completely. The issue was a corrupted root key file that Unbound uses for DNSSEC validation.
What happened: The /var/lib/unbound/root.key
file became corrupted, preventing Unbound from validating DNSSEC responses. With harden-dnssec-stripped: yes
enabled, this caused DNS queries to fail for domains that claim to be DNSSEC-signed.
Solution: Rebuild the root key file:
# Remove the corrupted root key
sudo rm /var/lib/unbound/root.key
# Regenerate the root key
sudo unbound-anchor -a /var/lib/unbound/root.key
# Set proper ownership
sudo chown unbound:unbound /var/lib/unbound/root.key
# Restart Unbound
sudo systemctl restart unbound
Why this matters: The root key is essential for DNSSEC validation. Without it, Unbound can’t verify that DNS responses are authentic and haven’t been tampered with.
Issue 3: DNS resolution failures
Problem: Some domains weren’t resolving after switching to recursive DNS.
Solution: Check Unbound logs and ensure root hints are properly downloaded. Most resolution issues stem from network connectivity problems, firewall rules, or DNSSEC validation issues rather than domain-specific requirements.
Issue 4: Certificate renewal automation
Problem: Let’s Encrypt certificates need to be renewed every 90 days, and the renewal process needs to restart Pi-hole services.
Solution: Set up automated certificate renewal with proper service restart hooks.
Issue 5: Performance tuning
Problem: Initial DNS queries were slow after switching to recursive DNS.
Solution: Tune Unbound cache settings and enable prefetching for frequently accessed domains.
The results: what I gained
After completing the setup, I have a robust, privacy-focused DNS infrastructure:
Privacy improvements
- No DNS queries logged by external providers
- Complete control over DNS resolution
- No tracking or analytics from DNS providers
Performance benefits
- Faster local queries due to caching
- Reduced latency for frequently accessed domains
- Better reliability with multiple Pi-hole servers
Operational benefits
- Automated replication across multiple servers
- Centralized management of DNS records
- Easy monitoring and troubleshooting
What I learned
This project taught me several important lessons:
Planning matters
DNS infrastructure is critical—plan for redundancy and automation from the start. I initially set up a single Pi-hole server, but quickly realized I needed multiple servers for reliability.
Documentation is essential
Document every configuration change and troubleshooting step. When things go wrong (and they will), having detailed notes saves hours of debugging.
Automation reduces errors
Manual processes are error-prone and time-consuming. Automating certificate renewal, DNS replication, and configuration management prevents many common issues.
Testing is crucial
Test every component thoroughly before going live. DNS issues can be subtle and hard to diagnose, so comprehensive testing is essential.
Getting started: my recommendations
If you’re considering setting up Pi-hole + Unbound, here’s my advice:
Start simple
Begin with a single Pi-hole server and basic configuration. Get familiar with the tools before adding complexity like TLS, replication, and automation.
Plan for growth
Design your infrastructure to scale. Even if you start with one server, plan for multiple servers and automated replication.
Document everything
Keep detailed notes of your configuration and troubleshooting steps. You’ll thank yourself later when something breaks.
Test thoroughly
Test DNS resolution, failover, and recovery procedures. Don’t assume everything will work perfectly.
Monitor continuously
Set up monitoring and alerting for your DNS infrastructure. DNS issues can be subtle but have widespread impact.
The bottom line
Pi-hole + Unbound provides excellent privacy and control, but requires careful planning and troubleshooting. The setup process isn’t trivial, but the benefits are worth the effort.
My setup now provides:
- Complete DNS privacy with recursive resolution
- Robust infrastructure with multiple servers and automated replication
- Easy management with centralized configuration and monitoring
- Reliable operation with automated certificate renewal and failover
The key is to start simple, plan for growth, and document everything. With proper planning and execution, you can build a DNS infrastructure that provides both privacy and reliability.
DNS doesn’t have to be complicated, but it does require attention to detail. The effort you put into setting up Pi-hole + Unbound will pay dividends in privacy, performance, and control.
Ready to Transform Your Career?
Let's work together to unlock your potential and achieve your professional goals.