Contents

Authoritative DNS with BIND on LXC


Prerequisites


Overview

Running BIND9 on LXC containers gives the lab a proper authoritative DNS server for internal names, with a secondary for redundancy. Each container is placed on a separate Proxmox node so a node failure does not take down both servers.

VyOS already acts as the DNS resolver for lab hosts. Rather than replacing it, the VyOS forwarder is updated to delegate lab.local queries to BIND while continuing to forward public queries upstream directly.


Creating the LXC Containers

Create two containers — one on each Proxmox node. In the Proxmox web UI, use Create CT with the following settings for each:

SettingValue
TemplateDebian 12
Disk4 GB
CPU1 core
Memory256 MB
Networklab bridge, static IP
Start at bootyes

Assign static IPs from outside the DHCP range — for example 10.x.x.2 (ns1, primary node) and 10.x.x.3 (ns2, secondary node). DNS servers must have stable addresses.


Installing BIND9

Run the following on both containers:

1
apt update && apt install -y bind9 bind9utils

Configuring the Primary (ns1)

Options

Edit /etc/bind/named.conf.options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
options {
    directory "/var/cache/bind";

    recursion yes;
    allow-recursion { 10.x.x.0/24; localhost; };

    forwarders {
        1.1.1.1;
        8.8.8.8;
    };

    allow-transfer { none; };

    dnssec-validation auto;
    listen-on { 127.0.0.1; 10.x.x.2; };
};
  • allow-recursion — limits recursive queries to the lab subnet; prevents the server being used as an open resolver
  • forwarders — upstream resolvers for names outside lab.local
  • allow-transfer none — zone transfers are blocked globally and enabled per-zone below

Zone Declaration

Edit /etc/bind/named.conf.local:

1
2
3
4
5
6
7
zone "lab.local" {
    type primary;
    file "/etc/bind/zones/db.lab.local";
    allow-transfer { 10.x.x.3; };
    notify yes;
    also-notify { 10.x.x.3; };
};
  • notify yes — sends a NOTIFY message to all NS records in the zone when it changes
  • also-notify — explicitly notifies the secondary regardless of whether it appears in the NS records; useful when the secondary’s IP differs from the name in DNS, or to be unambiguous about which server to notify

Zone File

Create the directory and the zone file:

1
mkdir -p /etc/bind/zones

/etc/bind/zones/db.lab.local:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$TTL    3600
@       IN      SOA     ns1.lab.local. hostmaster.lab.local. (
                        2026031201  ; Serial — increment on every change
                        3600        ; Refresh
                        900         ; Retry
                        604800      ; Expire
                        300 )       ; Negative TTL

; Name servers
@       IN      NS      ns1.lab.local.
@       IN      NS      ns2.lab.local.

; Name server addresses
ns1     IN      A       10.x.x.2
ns2     IN      A       10.x.x.3

; Lab hosts
router  IN      A       10.x.x.1
nas     IN      A       10.x.x.10

Validate and restart:

1
2
3
named-checkconf
named-checkzone lab.local /etc/bind/zones/db.lab.local
systemctl restart bind9

Configuring the Secondary (ns2)

Options

Use the same /etc/bind/named.conf.options as the primary but change listen-on to the ns2 IP:

1
listen-on { 127.0.0.1; 10.x.x.3; };

The secondary does not need forwarders — it will receive zone data from the primary and for non-local queries it can forward to upstream directly or rely on the primary. Keep the forwarders block identical for consistency.

Zone Declaration

Edit /etc/bind/named.conf.local on ns2:

1
2
3
4
5
zone "lab.local" {
    type secondary;
    file "/var/cache/bind/db.lab.local";
    primaries { 10.x.x.2; };
};

Restart:

1
2
named-checkconf
systemctl restart bind9

The secondary will immediately request a zone transfer from the primary. Confirm it succeeded:

1
journalctl -u named --no-pager | grep "transfer of"

Updating VyOS to Use BIND

The VyOS forwarder currently sends all queries to public upstream resolvers. Add a domain override so lab.local queries are sent to BIND instead:

1
2
3
4
5
6
7
configure

set service dns forwarding domain lab.local server '10.x.x.2'
set service dns forwarding domain lab.local server '10.x.x.3'

commit
save

Public queries continue to use the configured name-server entries. Only lab.local is delegated to BIND. No changes to DHCP are needed — lab hosts still receive the VyOS IP as their resolver.


Verifying

From a lab host:

1
2
3
4
5
# Should return the A record from the BIND zone file
dig nas.lab.local

# Should still resolve via upstream
dig google.com

From the Proxmox host or any machine that can reach ns1 directly:

1
2
3
4
5
# Query ns1 directly
dig @10.x.x.2 nas.lab.local

# Query ns2 — confirms zone transfer succeeded
dig @10.x.x.3 nas.lab.local

Adding Records

To add a new host, edit /etc/bind/zones/db.lab.local on ns1, increment the serial number, then reload the zone. BIND sends a NOTIFY to ns2 automatically, triggering a zone transfer.

1
2
3
# After editing the zone file on ns1:
named-checkzone lab.local /etc/bind/zones/db.lab.local
rndc reload lab.local

rndc reload lab.local reloads only the affected zone without restarting the daemon, and sends NOTIFY to the addresses in also-notify.


What’s Next

Now that internal hostnames resolve correctly, connections to those services should be secured with HTTPS. Because lab.local is a private domain, Let’s Encrypt cannot issue certificates for it — there is no way to complete a public ACME challenge against an internal name. The solution is to run your own CA and distribute its root certificate to your nodes and browsers.

  • Internal CA with OpenSSL — creating a root CA and intermediate CA for issuing TLS certificates for internal services
  • VRRP redundancy — adding a second VyOS instance for failover
  • BGP peering with Kubernetes — dynamically routing Kubernetes service IPs through VyOS