Home DNS Environment with High Availability
Post
Cancel

DNS Environment with High Availability

banner1 This project aims to create a comprehensive 3-tier DNS environment using Pi-hole, Unbound, Nebula-Sync, and Keepalived technologies. The system will be self-hosted, open source, and designed for high availability.

Lvl 1: Basic DNS Setup - This level consists of Pi-hole and Unbound running on a single device. Pi-hole is a network-wide DNS ad blocker that filters malicious domains, while Unbound is a recursive DNS resolver that queries authoritative servers directly for enhanced security and privacy.

Lvl 2: Redundant Multi-Node Setup - This level implements redundancy using two or more nodes on separate devices, each running Pi-hole and Unbound. Nebula-Sync (deployed via Docker) automatically synchronizes configurations, blocklists, and settings across all nodes to maintain consistent filtering behavior throughout the cluster.

Lvl 3: Load Balancing and Failover - This level adds load balancing and automatic failover using Keepalived, which manages a virtual IP address for client DNS requests. When the master node fails, Keepalived automatically promotes a backup node and transfers the virtual IP, ensuring seamless service continuity. This creates enterprise-grade DNS infrastructure with high availability while remaining completely self-hosted and open source.


Note: All installations in this guide use Debian-based distributions. If you’re using a different Linux distribution, please search for the appropriate package manager commands for your system.


Lvl 1: Basic DNS Setup

First, regardless of whether you’re using LXC, VM, or bare metal, configure a static IP address on your device.

Install Unbound

1
2
sudo apt update
sudo apt install unbound -y

Configure Unbound

1
sudo vim /etc/unbound/unbound.conf.d/pi-hole.conf

Pi-hole.conf configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#https://docs.pi-hole.net/guides/dns/unbound/
server:
    # If no logfile is specified, syslog is used
    # logfile: "/var/log/unbound/unbound.log"
    verbosity: 0

    interface: 127.0.0.1
    port: 5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes

    # May be set to no if you don't have IPv6 connectivity
    do-ip6: no
    prefer-ip6: no

    # Use this only when you downloaded the list of primary root servers!
    # If you use the default dns-root-data package, unbound will find it automatically
    #root-hints: "/var/lib/unbound/root.hints"

    # Trust glue only if it is within the server's authority
    harden-glue: yes

    # Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
    harden-dnssec-stripped: yes

    # Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
    # see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
    use-caps-for-id: no
    # buffer size. This value has also been suggested in DNS Flag Day 2020.
    edns-buffer-size: 1232

    # Perform prefetching of close to expired message cache entries
    # This only applies to domains that have been frequently queried
    prefetch: yes
    num-threads: 1

    # Ensure kernel buffer is large enough to not lose messages in traffic spikes
    so-rcvbuf: 1m

    # Ensure privacy of local IP ranges
    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

    # Ensure no reverse queries to non-public IP ranges (RFC6303 4.2)
    private-address: 192.0.2.0/24
    private-address: 198.51.100.0/24
    private-address: 203.0.113.0/24
    private-address: 255.255.255.255/32
    private-address: 2001:db8::/32

Restart Unbound

1
sudo service unbound restart

For more information on configuration, visit: https://docs.pi-hole.net/guides/dns/unbound/

Install Pi-hole

1
curl -sSL https://install.pi-hole.net | bash

pic5

Configure Pi-hole to use Unbound

pic3 During the installation wizard, when prompted for DNS provider, select “Custom” and add:

1
127.0.0.1#5335

Set Pi-hole Password

After completing the wizard installation, set your admin password:

1
sudo pihole setpassword

Access Pi-hole Admin Panel

To access the Pi-hole admin panel, navigate to:

1
http://<your-ip>/admin/login

Replace <your-ip> with your device’s static IP address.


Lvl 2: Redundant Multi-Node Setup

For Lvl 2, we will use nebula-sync in a Docker container that will update the configuration every hour of your Pi-hole, and also we have to find another device in a different physical place and install a second Pi-hole and Unbound using the same steps from Lvl 1 above.

Deployment Options

Option 1: Docker Compose

1
2
3
4
5
6
7
8
9
10
11
services:
  nebula-sync:
    image: ghcr.io/lovelaze/nebula-sync:latest
    container_name: nebula-sync
    restart: unless-stopped
    environment:
      - PRIMARY=http://ph1.example.com|password
      - REPLICAS=http://ph2.example.com|password
      - FULL_SYNC=true
      - RUN_GRAVITY=true
      - CRON=0 * * * *

Option 2: Docker CLI

1
2
3
4
5
6
7
8
9
docker container run -d \
  --name nebula-sync \
  -e PRIMARY="http://ph1.example.com|password" \
  -e REPLICAS="http://ph2.example.com|password" \
  -e FULL_SYNC=true \
  -e RUN_GRAVITY=true \
  -e CRON="0 * * * *" \
  --restart unless-stopped \
  ghcr.io/lovelaze/nebula-sync:latest

Configuration Notes

  • PRIMARY: This should be your master Pi-hole instance
  • REPLICAS: These are the secondary nodes where all configurations will be replicated
  • Important: If you modify configurations on replica Pi-hole instances, those changes will NOT sync back to the primary Pi-hole
  • Multiple Replicas: You can add multiple replica nodes by separating them with commas:

    1
    
      http://ph2.example.com|password,http://ph3.example.com|password
    

Replace the example URLs with your actual Pi-hole IP addresses and use the admin passwords you configured in Lvl 1.


Lvl 3: Load Balancing and Failover

For Lvl 3, we will use Keepalived to create a virtual IP address that provides automatic failover between our Pi-hole nodes. This ensures that if the master node goes down, the backup node automatically takes over DNS services without any manual intervention.

Installation

1
sudo apt install keepalived libipset13 -y

Network Configuration

Before starting the configuration, you need to identify the static IP addresses of your nodes. Use this command to find your network interface and IP address:

1
ip -br -4 -c a

Save the interface name (e.g., eth0, ens18) and IP address for each node.

Configure Keepalived

Edit the keepalived configuration file:

1
sudo vim /etc/keepalived/keepalived.conf

Master Node Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vrrp_instance VI_1 {
  state MASTER
  interface eth0
  virtual_router_id 55
  priority 200
  advert_int 1
  unicast_src_ip 10.0.0.10
  unicast_peer {
    10.0.0.20
  }

  authentication {
    auth_type PASS
    auth_pass C3P9K9gc
  }

  virtual_ipaddress {
    10.0.0.100/24
  }
}

Backup Node Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vrrp_instance VI_1 {
  state BACKUP
  interface eth0
  virtual_router_id 55
  priority 100
  advert_int 1
  unicast_src_ip 10.0.0.20
  unicast_peer {
    10.0.0.10
  }

  authentication {
    auth_type PASS
    auth_pass C3P9K9gc
  }

  virtual_ipaddress {
    10.0.0.100/24
  }
}
  • unicast_src_ip: IP address of this node (the one running the config).

  • unicast_peer: IP address of the other node (used for failover communication).

  • priority: Determines which node becomes active—higher values mean higher priority. When the master fails, the node with the next highest priority takes over.

  • auth_pass: Password used for VRRP authentication. It must be exactly 8 characters or fewer—longer passwords will be ignored or cause errors.

Enable and Start Services

Now that your load balancer is configured, restart the service and ensure it starts automatically:

1
2
sudo systemctl enable keepalived.service
sudo systemctl restart keepalived.service

Final Configuration

Your entire system is now set up. Configure your router’s DNS settings to use the virtual IP address, which in this example is 10.0.0.100. This virtual IP will automatically switch between your Pi-hole nodes as needed for high availability.


Additional Resources

Tutorials:

Documentation:

This post is licensed under CC BY 4.0 by the author.