Every internet transaction starts with a DNS lookup. Whether it is a browser requesting a website, a mobile app sending diagnostic telemetry, or a smart TV phoning home viewing statistics, domain name resolution is the front door of your network.

Commercial upstream resolvers (Google DNS, Cloudflare, ISP DNS) log domain requests and retain usage metadata. By pairing Pi-hole (for ad and tracker sinkholing) with Unbound (for local recursive root zone resolution), you eliminate upstream telemetry and protect your entire network without browser extensions.


High-Availability Architecture (Primary + Secondary)

Running a single DNS server creates a single point of failure. If that node reboots or crashes, internet connectivity halts for all connected devices.

                  ┌───────────────────────────────┐
                  │       Client Request          │
                  └───────┬───────────────┬───────┘
                          │               │
            Primary DNS   ▼               ▼  Secondary DNS
             ┌─────────────────┐     ┌─────────────────┐
             │ Pi-hole #1      │     │ Pi-hole #2      │
             │ (10.10.10.5)    │     │ (10.10.10.6)    │
             └────────┬────────┘     └────────┬────────┘
                      │ Gravity-Sync          │
                      │ ◄───────────────────► │
                      ▼                       ▼
             ┌─────────────────┐     ┌─────────────────┐
             │ Unbound #1      │     │ Unbound #2      │
             │ (127.0.0.1:5335)│     │ (127.0.0.1:5335)│
             └────────┬────────┘     └────────┬────────┘
                      │                       │
                      └───────────┬───────────┘
                                  ▼
                   Root DNS Servers (.) & TLDs

We deploy two Raspberry Pi or lightweight Alpine Linux VM nodes:

  • Node 1 (Primary): 10.10.10.5
  • Node 2 (Secondary): 10.10.10.6

  • Configuring Unbound as a Recursive Resolver

    Rather than forwarding blocked requests to third-party providers, Unbound queries the 13 root name servers directly, traversing Top-Level Domain (TLD) servers until it receives an authoritative answer.

    Install and configure Unbound (/etc/unbound/unbound.conf.d/pi-hole.conf):

    conf
    server:
        verbosity: 1
        interface: 127.0.0.1
        port: 5335
        do-ip4: yes
        do-udp: yes
        do-tcp: yes
        do-ip6: no
    
        # Use root hints file
        root-hints: "/var/lib/unbound/root.hints"
    
        # Trust anchor file for DNSSEC validation
        auto-trust-anchor-file: "/var/lib/unbound/root.key"
    
        # Buffer and cache tuning
        so-rcvbuf: 4m
        so-sndbuf: 4m
        msg-cache-size: 64m
        rrset-cache-size: 128m
        infra-cache-numhosts: 10000
    
        # Privacy protections
        hide-identity: yes
        hide-version: yes
        harden-glue: yes
        harden-dnssec-stripped: yes
        use-caps-for-id: no
        edns-buffer-size: 1232
        prefetch: yes
        num-threads: 2

    Start the service and verify DNSSEC validation:

    bash
    sudo systemctl enable --now unbound
    dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335 # Must return SERVFAIL
    dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335   # Must return NOERROR

    Synchronizing Pi-hole Nodes with Gravity-Sync

    When you whitelist a domain or add blocklists on your primary Pi-hole, having to manually replicate those changes on the backup node is error-prone. Gravity-Sync automates database synchronization over SSH.

  • Install Gravity-Sync on both nodes:
  • bash
    curl -sSL https://gravity.vmstan.com | bash
  • Run gravity-sync setup on the primary node. It will exchange SSH keys, link local SQLite databases (gravity.db and custom.list), and establish a 30-minute systemd synchronization timer.
  • Configure your router's DHCP options to broadcast both 10.10.10.5 and 10.10.10.6 as DNS servers. Your network now enjoys redundant, zero-telemetry, ad-sinkholed name resolution.