← Back
Date

🌐 Self-Hosted DNS Server (Pi-hole + Unbound)

DNS Server Architecture

A robust, privacy-focused DNS infrastructure combining Pi-hole for ad-blocking and Unbound for recursive DNS resolution. This setup provides complete DNS independence, enhanced privacy, and network-wide ad blocking.

🚀 Overview

This project implements a self-hosted DNS resolution stack that eliminates dependence on external DNS providers. By combining Pi-hole (network-wide ad/tracker blocking) with Unbound (recursive DNS resolver), the system provides:

  • Complete Privacy: No DNS query logging by third parties
  • Enhanced Performance: Local caching for faster resolution
  • Ad-Free Browsing: Network-wide blocking of ads and trackers
  • Security: DNSSEC validation and encrypted upstream communication

⚡ Features

🔒 Privacy & Security

  • Recursive DNS Resolution: Queries start from root servers
  • DNSSEC Validation: Ensures DNS response authenticity
  • No Logging: Self-hosted means no third-party data collection

🚀 Performance

  • Local Caching: Redundant query resolution in milliseconds
  • Aggressive Caching: TTL optimization for frequent domains

📖 Detailed Setup Guide

Step 1: LXC Container Setup

# Create new LXC container (adjust ID and storage as needed)
pct create 100 debian-12-standard_12.2-1_amd64.tar.zst \
  --storage local-lvm \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --memory 512 \
  --cores 1

# Start container and set static IP
pct start 100
pct set 100 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.10/24,gw=192.168.1.1

Step 2: Pi-hole Installation

# Update system and install Pi-hole
apt update && apt upgrade -y
curl -sSL https://install.pi-hole.net | bash

Step 3: Unbound Installation & Configuration

# Install Unbound
apt install unbound unbound-anchor -y

# Download root hints
curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache

# Create unbound configuration
cat > /etc/unbound/unbound.conf.d/pi-hole.conf << 'EOF'
server:
    verbosity: 1
    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    prefer-ip6: no
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: yes
    edns-buffer-size: 1472
    prefetch: yes
    num-threads: 1
    so-rcvbuf: 1m
    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

    # Root hints
    root-hints: "/var/lib/unbound/root.hints"

    # DNSSEC
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    val-log-level: 2

    # Cache settings
    key-cache-size: 64m
    key-cache-slabs: 4
    neg-cache-size: 32m
    msg-cache-size: 32m
    msg-cache-slabs: 4
    rrset-cache-size: 64m
    rrset-cache-slabs: 4
    cache-min-ttl: 3600
    cache-max-ttl: 86400

    # Security
    unwanted-reply-threshold: 10000000
    val-nsec-type: "proof"
    serve-expired: yes
    serve-expired-ttl: 3600
    aggressive-nsec: yes
EOF

# Initialize root anchor and set permissions
unbound-anchor -a /var/lib/unbound/root.key
chown unbound:unbound /var/lib/unbound/root.hints
chown unbound:unbound /var/lib/unbound/root.key

# Start and enable Unbound
systemctl enable unbound
systemctl start unbound

# Test Unbound
dig @127.0.0.1 -p 5335 google.com

Step 4: Configure Pi-hole to Use Unbound

  1. Access Pi-hole admin panel: http://your-container-ip/admin
  2. Navigate to Settings > DNS
  3. Remove all upstream DNS servers
  4. Add custom upstream DNS: 127.0.0.1#5335
  5. Enable Use DNSSEC and Use conditional forwarding if needed

Step 5: Router Configuration

Configure your router to use the Pi-hole container as the primary DNS server:

  • DHCP DNS Settings: Set to your container's IP (e.g., 192.168.1.10)

🐛 Troubleshooting

Common Issues & Solutions

DNS Resolution Failing

# Test Unbound resolution
dig @127.0.0.1 -p 5335 example.com

# Check Unbound logs
journalctl -u unbound -f

Service Not Starting

# Check service status
systemctl status unbound
systemctl status pihole-FTL

# Verify configuration syntax
unbound-checkconf /etc/unbound/unbound.conf.d/pi-hole.conf

📊 Monitoring & Maintenance

# Check service status
systemctl status pihole-FTL
systemctl status unbound

# Update blocklists
pihole -g

# Update Unbound root hints
curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache
systemctl restart unbound

📄 License

This project is licensed under the MIT License.

🌐 Connect

Taki Sadik

Last updated: March 2024
Maintained with ❤️ by Taki Sadik

⭐ If this project helped you, please consider giving it a star on GitHub!