
Putting Bind inside an unprivileged Linux container keeps the Proxmox host surface small while letting us
roll snapshots, firewall the service separately and move the DNS stack to another node in seconds.
Arch Linux was picked for its tiny base image and bleeding‑edge packages — ideal when you actually
want the latest CVE fixes. If you prefer a long‑term‑support distro (Debian, Alma …), the steps are
95 % identical: just swap pacman
for apt
/dnf
and enjoy.
Download an official Arch template:
pveam update
pveam available | grep archlinux
pveam download local archlinux-20240301_amd64.tar.zst
Spin up an unprivileged container (ID 130, 512 MiB RAM, static IP 10.0.0.3):
pct create 130 local:vztmpl/archlinux-20240301_amd64.tar.zst \
--hostname dns01 \
--unprivileged 1 \
--net0 name=eth0,bridge=vmbr0,ip=10.0.0.3/24,gw=10.0.0.1 \
--memory 512 --swap 512 --rootfs local-lvm:5
pct start 130
pct console 130
Need systemd-nspawn
or FUSE later? Enable nesting capabilities:
pct set 130 -features nesting=1
Refresh keys, fully upgrade Arch
pacman -Sy archlinux-keyring --noconfirm
pacman -Syu --noconfirm
Install Bind and dig / nsupdate helpers
pacman -S --noconfirm bind bind-tools
Free port 53 – disable the default stub resolver:
systemctl disable --now systemd-resolved.service
Enable & start named
systemctl enable --now named.service
pacman -Syu
swaps the kernel while removing the old
modules. Either reboot right after, or install the hook below so modules persist until next boot.
pacman -Sy kernel-modules-hook
Arch’s iptables
preset is blank. If your LXC is bridged straight to the LAN it will happily answer
the whole internet. The one‑liner below allows only TCP/UDP 53 plus SSH from the LAN range:
pacman -S --noconfirm nftables
cat > /etc/nftables.conf <<'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
ct state established,related accept
iif "lo" accept
ip saddr 10.0.0.0/24 udp dport 53 accept # DNS UDP
ip saddr 10.0.0.0/24 tcp dport 53 accept # DNS TCP
counter drop
}
}
EOF
systemctl enable --now nftables
/etc/named.conf # main configuration, world‑readable
/etc/named/ # custom include files (our views)
/var/named/ # zone files, owned by named:named (chroot‑safe)
/run/named/ # PID & runtime artefacts
/var/log/named.log # we create this in logging section
The Arch package does not chroot Bind. That makes debugging easier. If you want a real chroot use
named -t
or systemd‑nspawn.
named.conf
(split‑horizon)Below is a hardened template: recursion disabled by default, query logging on its own file and two views (“lan”/“wan”) for split‑horizon. Adapt CIDRs and domain.
options {
directory "/var/named";
pid-file "/run/named/named.pid";
listen-on { any; };
listen-on-v6 { any; };
recursion no; # safety first
allow-recursion { 10.0.0.0/24; };
allow-transfer { none; };
allow-update { none; };
dnssec-validation auto;
version "not disclosed";
hostname "dns01";
server-id none;
};
logging {
channel querylog {
file "/var/log/named.log" versions 3 size 5m;
severity info;
print-category yes;
print-time yes;
print-severity yes;
};
category queries { querylog; };
};
view "lan" {
match-clients { 10.0.0.0/24; localhost; };
include "/etc/named/lan-zones.conf";
};
view "wan" {
match-clients { any; };
recursion no; # public must not recurse
include "/etc/named/wan-zones.conf";
};
match-clients
that succeeds decides which
zones are visible and whether recursion is allowed. Put the most restrictive view first.
Add a per‑view include so you can host many domains without editing the root config each time:
# /etc/named/lan-zones.conf
zone "example.com" IN { type master; file "lan/example.com.zone"; };
# /etc/named/wan-zones.conf
zone "example.com" IN { type master; file "wan/example.com.zone"; };
LAN (private) zone /var/named/lan/example.com.zone
$TTL 2h
@ IN SOA ns1.example.com. hostmaster.example.com. (
2025051701 ; Serial (YYYYMMDDnn)
8h ; Refresh
30m ; Retry
1w ; Expire
1h ) ; Negative Cache TTL
IN NS ns1.example.com.
ns1 IN A 10.0.0.3
@ IN A 10.0.0.20
www IN A 10.0.0.20
WAN (public) zone /var/named/wan/example.com.zone
$TTL 2h
@ IN SOA ns1.example.com. hostmaster.example.com. (
2025051701 ; Serial (YYYYMMDDnn)
8h ; Refresh
30m ; Retry
1w ; Expire
1h ) ; Negative Cache TTL
IN NS ns1.example.com.
ns1 IN A 203.0.113.15
@ IN A 203.0.113.15
www IN A 203.0.113.15
YYYYMMDDnn
. Bind ignores a zone whose serial did not change.
Validate syntax & zone integrity
named-checkconf
named-checkzone example.com /var/named/lan/example.com.zone
named-checkzone example.com /var/named/wan/example.com.zone
Reload without downtime
rndc reload
From a LAN client:
dig @10.0.0.3 www.example.com +short # → 10.0.0.20
From an external VPS:
dig @203.0.113.15 www.example.com +short # → 203.0.113.15
Verify AXFR is blocked
dig -t AXFR example.com @203.0.113.15 # → Transfer failed.
Confirm recursion disabled for WAN / LAN
dig @203.0.113.15 google.com +short # no answer section
dig @10.0.0.3 google.com +short # no answer section
pct set 130 -net0 ...,ip6=2a02:ffff::3/64
and add a AAAA record.pct snapshot 130 "after-example.com"
.vzdump 130 --mode snapshot --compress zstd
includes /etc & /var/named by default.Add a record, bump serial, reload
vim /var/named/lan/example.com.zone # edit + bump serial
rndc reload example.com
Flush full cache
rndc flush
Dump statistics
rndc stats && tail /var/named/named.stats
journalctl -u named.service
and named-checkconf
.listen-on
directives.timedatectl
).