Featured image of post Building a High-Availability DNS Cluster on Debian

Building a High-Availability DNS Cluster on Debian

High-Availability DNS Cluster: Technitium + Keepalived (IPVS) on Debain LXC Container

Domain Name System (DNS) is the backbone of any network infrastructure. When your DNS servers go down, your entire network effectively stops functioning. While many network administrators simply deploy two separate DNS servers and list both in their DHCP scope, this approach does not provide true load balancing or instantaneous failover.

In this guide, we will build a production-grade, highly available DNS cluster using two Technitium DNS servers running inside Proxmox Linux Containers (LXC). We will leverage Keepalived and IPVS (IP Virtual Server) to achieve both seamless load balancing and intelligent failover over a single Virtual IP (VIP) address.


Why Standard Secondary DNS Isn’t Enough

Most home labs and enterprise environments deploy two DNS servers (Primary and Secondary). They hand out both IP addresses via DHCP. However, the client-side implementation of this is flawed:

  1. Unpredictable Routing: Some operating systems (like Windows) stick to the primary server until it completely times out, causing severe delays during an outage. Other systems (like macOS/Linux) might query both semi-randomly, making traffic distribution unpredictable.
  2. No Real Load Balancing: Traffic is rarely distributed evenly between the two servers.
  3. Slow Failover: If the primary DNS server freezes but keeps its network link active, clients will waste precious seconds waiting for timeouts before trying the secondary server.

The Solution: Keepalived + IPVS

By introducing Keepalived with IPVS at Layer 4, we create a single Virtual IP (VIP). Your clients only ever talk to this one IP address.

Keepalived acts as an intelligent traffic director:

  • Load Balancing: It splits incoming UDP and TCP port 53 DNS queries evenly across both Technitium instances using a Round Robin algorithm.
  • Instant Failover: It continuously polls the health of each Technitium instance. If one container or DNS service crashes, Keepalived immediately removes it from the rotation.
  • Infrastructure Redundancy: Keepalived uses VRRP (Virtual Router Redundancy Protocol). If the primary load-balancing container dies entirely, the secondary container instantly assumes the VIP within milliseconds.

Schematic Overview

[ NETWERK CLIENTS ]
(Request DNS via VIP)
 ┌────────────────┐
 │  VIRTUEL IP   │
 │  192.168.1.10  │
 └───────┬────────┘
  ┌──────┴──────┐  (Keepalived VRRP / IPVS)
  │             │  (Divide UDP/TCP requests via Round Robin)
  ▼             ▼
┌─────────────────┐   ┌─────────────────┐
│ LXC Container 1 │   │ LXC Container 2 │
│    [MASTER]     │   │    [BACKUP]     │
│   192.168.1.11  │   │   192.168.1.12  │
├─────────────────┤   ├─────────────────┤
│  Technitium #1  │   │  Technitium #2  │
│  (lo: .1.10/32) │   │  (lo: .1.10/32) │
└────────┬────────┘   └────────┬────────┘
         │                     │
         └──────────┬──────────┘
                    │  (Direct Routing - DR Mode)
             [ Direct Answer ]
             (Back to Clients)

Explanation of the data flow:

  1. Inbound traffic: Network clients send all their DNS requests exclusively to the Virtual IP (192.168.1.10).
  2. Load Balancing: The container currently holding the MASTER role (Container 1) intercepts the traffic and distributes the requests across the two Technitium DNS backends using the Linux IPVS kernel module (Round Robin).
  3. Direct Routing (DR): Thanks to the loopback configuration (lo: 192.168.1.10/32), both containers process the packets directly and send the response straight back to the client, bypassing the load balancer. This ensures minimal latency.
  4. Failover: If Container 1 fails, Keepalived moves the VIP to Container 2 within a fraction of a second; from that point on, Container 2 handles the requests independently.

Architectural Overview

  • Virtual IP (VIP): 192.168.1.10 (The single DNS address given to your clients)
  • Technitium Container 1 (MASTER): 192.168.1.11
  • Technitium Container 2 (BACKUP): 192.168.1.12

Step 1: Preparing the Proxmox Host (Crucial for LXC)

Because LXC containers share the host’s Linux kernel, they cannot load kernel modules or manipulate network routing infrastructure by default. We must prepare the Proxmox host first.

1. Load IPVS Modules on the Host

Log into your Proxmox VE Host via SSH and run the following commands to load the required IPVS load-balancing kernel modules:

modprobe ip_vs
modprobe ip_vs_rr

To ensure these modules survive a Proxmox host reboot, append them to the host’s /etc/modules file:

echo "ip_vs" >> /etc/modules
echo "ip_vs_rr" >> /etc/modules

2. Adjust LXC Container Permissions

You must grant your containers permission to interact with the network stack. On your Proxmox host, open the configuration files for both containers (located at /etc/pve/lxc/YOUR_CONTAINER_ID.conf) and add these lines: Make sure the the LXC container are running in privilegded mode. Option “unprivileged: 0” in .conf

1
2
3
4
5
6
lxc.apparmor.raw: mount fstype=devpts,
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
# SAFE Capabilties (let the console alive, but give networks rights)
lxc.cap.drop: 
lxc.cap.keep: sys_admin net_admin chown dac_override fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_raw ipc_owner sys_chroot sys_ptrace sys_tty_config

Restart both LXC containers after saving these changes.


Step 2: Configuring the Loopback Interface on the Containers

We will be using Direct Routing (DR) mode in IPVS. This is the fastest method of load balancing because incoming traffic goes through the load balancer, but the backend Technitium servers reply directly to the client.

For this to work, both containers must accept packets destined for the VIP (192.168.1.10), but they must not broadcast this IP to the local network to avoid IP conflicts. We solve this by binding the VIP to the local loopback (lo) interface.

Inside both Debian LXC containers, open /etc/network/interfaces:

nano /etc/network/interfaces

Modify the loopback configuration block to match the following:

auto lo
iface lo inet loopback
    up ip addr add 192.168.1.10/32 dev lo
    down ip addr del 192.168.1.10/32 dev lo

Apply the changes instantly without a reboot:

ifdown lo && ifup lo

Step 3: Installing and Configuring Keepalived

Now, log into both containers to install Keepalived and the IPVS administration utility:

apt update && apt install -y keepalived ipvsadm

1. Configuration for Container 1 (MASTER)

Create the configuration file /etc/keepalived/keepalived.conf on your first Technitium container:

 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
55
56
57
58
59
60
61
62
63
64
65
66
global_defs {
    router_id dns_lb1
    enable_script_security
}

# 1. High Availability (VRRP Configuration)
vrrp_instance VI_1 {
    state MASTER
    interface eth0          # Replace with your actual network interface
    virtual_router_id 51
    priority 101            # Higher priority wins the VIP
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass A_Secure_Cluster_Password
    }
    virtual_ipaddress {
        192.168.1.10/24     # Your Virtual IP (VIP)
    }
}

# 2. Load Balancing for DNS UDP (Port 53)
virtual_server 192.168.1.10 53 {
    delay_loop 6
    lb_algo rr              # Round Robin distribution
    lb_kind DR              # Direct Routing mode
    protocol UDP

    real_server 192.168.1.11 53 {
        weight 1
        MISC_CHECK {
            misc_path "/usr/bin/nc -zu -w 1 127.0.0.1 53" # Local health check
            misc_timeout 2
        }
    }

    real_server 192.168.1.12 53 {
        weight 1
        MISC_CHECK {
            misc_path "/usr/bin/nc -zu -w 1 192.168.1.12 53" # Remote health check
            misc_timeout 2
        }
    }
}

# 3. Load Balancing for DNS TCP (Port 53)
virtual_server 192.168.1.10 53 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    protocol TCP

    real_server 192.168.1.11 53 {
        weight 1
        TCP_CHECK {
            connect_timeout 3
        }
    }

    real_server 192.168.1.12 53 {
        weight 1
        TCP_CHECK {
            connect_timeout 3
        }
    }
}

2. Configuration for Container 2 (BACKUP)

Create the exact same file on your second Technitium container, but change the vrrp_instance configuration block to reflect its backup status and lower priority:

    state BACKUP
    priority 100            # Lower than the MASTER server

3. Enable and Start Keepalived

Run this command on both containers to start the service and ensure it launches automatically at boot:

systemctl enable --now keepalived

Step 4: Verification and Testing

Your high-availability cluster is now live! Let’s verify that everything is working perfectly.

1. Check IP Allocation

On Container 1 (MASTER), run:

ip addr show eth0

You should see both its local IP (192.168.1.11) and the VIP (192.168.1.10). If you run the same command on Container 2, the VIP should not be present.

2. Inspect the Load Balancing Table

Run the following command on the container that is currently acting as the MASTER:

ipvsadm -ln

You will see a clean routing matrix mapping your VIP to both backend servers across TCP and UDP protocols.

3. Simulating a Failure

To test the failover capability, stop the Keepalived service on Container 1:

systemctl stop keepalived

If you run ip addr show eth0 on Container 2, you will notice that it has gracefully assumed control of the VIP (192.168.1.10) in a fraction of a second. Your network clients will experience zero downtime, and DNS queries will keep resolving seamlessly.


4. Monitoring Real-Time Traffic Distribution

Once your high-availability DNS cluster is running, you will want to see it in action. The best way to monitor how Keepalived and IPVS are distributing traffic between your Technitium servers is by using the following debugging command:

watch -n 0.5 ipvsadm -Ln

What it does

This command combines two powerful Linux utilities. The ipvsadm -Ln command lists the current IP Virtual Server kernel routing table in a clean, numeric format (-n), skipping slow DNS lookups. By wrapping it in watch -n 0.5, the terminal automatically refreshes the output every half-second, providing a near-real-time view of your live network traffic.

How to interpret the output When you run this on the active MASTER container, you will see an output structured like this:

IP Virtual Server version 1.2.1 (size=4096)

Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
UDP  192.168.1.10:53 rr
  -> 192.168.1.11:53              Route   1      0          142       
  -> 192.168.1.12:53              Route   1      0          139

Key metrics to look out for:

  • UDP/TCP … rr: Confirms that IPVS is listening on your Virtual IP (VIP) on port 53 and using the Round Robin (rr) scheduler.
  • Forward (Route): Indicates that Direct Routing (DR) is working properly.
  • Weight: A value of 1 means the server is healthy. If Keepalived detects that a Technitium instance is down, this weight will immediately drop to 0, removing it from rotation.
  • InActConn (Inactive Connections): Since DNS over UDP is stateless, queries do not stay open. Instead, they quickly register as inactive connections. Watch these numbers increment evenly on both rows as clients make requests—this is visual proof that your load balancer is dividing the work perfectly!

Conclusion

By coupling Technitium DNS with Keepalived and IPVS, you eliminate the classic pitfalls of primary/secondary DNS configurations. You get an enterprise-grade setup that guarantees absolute uptime, linear traffic distribution, and instantaneous failover management—all running within lightweight, low-overhead Proxmox LXC containers.