Authoritative + caching DNS server for andrewmercer.local, plus the domain-to-subdomain migration to dev.andrewmercer.com.
Install¶
sudo apt update
sudo apt install -y bind9
sudo systemctl enable --now bind9.service
Configuration¶
Debian/Ubuntu splits named.conf into includes so options, logging, and zone definitions can be edited independently.
/etc/bind/named.conf
include "/etc/bind/named.conf.logging";
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
/etc/bind/named.conf.options
acl "trusted" {
10.10.13.101;
10.10.13.102;
10.10.13.103;
10.10.13.104;
};
options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { trusted; };
listen-on { 10.10.13.5; };
listen-on-v6 { any; };
allow-transfer { trusted; };
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
};
Notes on this block:
- trusted covers the four LAN hosts allowed to use this server recursively and to pull zone transfers — anything not in that list is refused both, which is the correct default for a homelab server not meant to be internet-facing.
- listen-on binds only the specific LAN address (10.10.13.5) rather than every interface, which matters if this host has more than one NIC (e.g., a separate WAN-facing interface) — no need to expose the resolver there.
- auth-nxdomain no is the RFC 1035–conformant setting and should stay no unless a specific downstream tool depends on the older non-conformant behavior.
/etc/bind/named.conf.logging
The original version logged every category at debug — useful while getting BIND running the first time, but far too verbose (and disk-hungry) to leave on indefinitely, since queries/client/network/dispatch log every single lookup. Two versions below: the debug config for active troubleshooting, and a leaner steady-state config to switch to once things are working.
While troubleshooting:
logging {
channel "debug" {
file "/var/log/named/named.log" versions 2 size 50m;
print-time yes;
print-category yes;
};
category "default" { "debug"; };
category "general" { "debug"; };
category "database" { "debug"; };
category "security" { "debug"; };
category "config" { "debug"; };
category "resolver" { "debug"; };
category "xfer-in" { "debug"; };
category "xfer-out" { "debug"; };
category "notify" { "debug"; };
category "client" { "debug"; };
category "unmatched" { "debug"; };
category "network" { "debug"; };
category "update" { "debug"; };
category "queries" { "debug"; };
category "dispatch" { "debug"; };
category "dnssec" { "null"; };
category "lame-servers"{ "null"; };
};
Steady-state (switch to this once the server is confirmed working):
logging {
channel "default_log" {
file "/var/log/named/named.log" versions 5 size 20m;
severity notice;
print-time yes;
print-category yes;
};
channel "security_log" {
file "/var/log/named/security.log" versions 5 size 20m;
severity info;
print-time yes;
};
category default { default_log; };
category security { security_log; };
category config { default_log; };
category update { default_log; };
};
Either way, the log directory must be owned by the bind user or named fails at startup with a permission error (see Troubleshooting below):
sudo mkdir -p /var/log/named
sudo chown bind:bind /var/log/named
/etc/bind/named.conf.local
// Consider adding the 1918 zones here, if they are not used in your organization
include "/etc/bind/zones.rfc1918";
zone "andrewmercer.local" {
type master;
file "/etc/bind/db.andrewmercer.local";
allow-update { trusted; };
};
zone "13.10.10.in-addr.arpa" {
type master;
file "/etc/bind/db.13.10.10";
allow-update { trusted; };
};
/etc/bind/db.andrewmercer.local (forward zone)
$TTL 604800
@ IN SOA ns1.andrewmercer.local. admin.ns1.andrewmercer.local. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative cache TTL
; NS Records
IN NS ns1.andrewmercer.local.
IN NS ns2.andrewmercer.local.
IN NS ns3.andrewmercer.local.
IN NS ns4.andrewmercer.local.
; A Records
ns1.andrewmercer.local. IN A 10.10.13.5
ns2.andrewmercer.local. IN A 10.10.13.6
ns3.andrewmercer.local. IN A 10.10.13.7
ns4.andrewmercer.local. IN A 10.10.13.8
; CNAME Records
docker-repo.andrewmercer.local. IN CNAME andrewmercer1.andrewmercer.local.
Note: docker-repo points via CNAME at andrewmercer1.andrewmercer.local, which doesn't otherwise appear in this zone file — confirm that name is defined elsewhere (another zone, or a missing A record here) before relying on it, since a CNAME to a non-existent target resolves to nothing.
/etc/bind/db.13.10.10 (reverse zone for 10.10.13.0/24)
$TTL 604800
@ IN SOA andrewmercer.local. admin.andrewmercer.local. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative cache TTL
IN NS ns1.andrewmercer.local.
IN NS ns2.andrewmercer.local.
IN NS ns3.andrewmercer.local.
IN NS ns4.andrewmercer.local.
5.13.10.10.in-addr.arpa. IN PTR ns1.andrewmercer.local.
6.13.10.10.in-addr.arpa. IN PTR ns2.andrewmercer.local.
7.13.10.10.in-addr.arpa. IN PTR ns3.andrewmercer.local.
8.13.10.10.in-addr.arpa. IN PTR ns4.andrewmercer.local.
Note: the forward zone's SOA MNAME is ns1.andrewmercer.local. but the reverse zone's SOA MNAME is andrewmercer.local. (not ns1...) — worth making these consistent, since the SOA's primary-server field is conventionally the actual name server hostname, not the bare zone name.
Reminder for any future edit to either file: bump the Serial before reloading, or secondaries/caches won't notice the change.
Apply and check:
sudo named-checkconf
sudo named-checkzone andrewmercer.local /etc/bind/db.andrewmercer.local
sudo named-checkzone 13.10.10.in-addr.arpa /etc/bind/db.13.10.10
sudo rndc reload
Migrating andrewmercer.local → dev.andrewmercer.com¶
Converting the internal zone into a proper subdomain of a real registered domain. This is a rename of the zone plus new delegation, not a special "conversion" record type — see the general BIND guide, §10, for the underlying steps this follows.
/etc/bind/db.dev.andrewmercer.com
$TTL 604800
@ IN SOA ns1.dev.andrewmercer.com. admin.ns1.dev.andrewmercer.com. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative cache TTL
; NS Records
IN NS ns1.dev.andrewmercer.com.
IN NS ns2.dev.andrewmercer.com.
IN NS ns3.dev.andrewmercer.com.
IN NS ns4.dev.andrewmercer.com.
; A Records
ns1.dev.andrewmercer.com. IN A 10.10.13.5
ns2.dev.andrewmercer.com. IN A 10.10.13.6
ns3.dev.andrewmercer.com. IN A 10.10.13.7
ns4.dev.andrewmercer.com. IN A 10.10.13.8
; CNAME Records
docker-repo.dev.andrewmercer.com. IN CNAME andrewmercer1.dev.andrewmercer.com.
(Fixed from the original draft: the SOA's admin/RNAME field was admin.ns1.dev.andrewmercer.local — mixing the new .com names with the old .local domain. It should reference the new namespace consistently, admin.ns1.dev.andrewmercer.com., as above.)
/etc/bind/db.13.10.10 (reverse zone, updated to match)
$TTL 604800
@ IN SOA dev.andrewmercer.com. admin.dev.andrewmercer.com. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative cache TTL
IN NS ns1.dev.andrewmercer.com.
IN NS ns2.dev.andrewmercer.com.
IN NS ns3.dev.andrewmercer.com.
IN NS ns4.dev.andrewmercer.com.
5.13.10.10.in-addr.arpa. IN PTR ns1.dev.andrewmercer.com.
6.13.10.10.in-addr.arpa. IN PTR ns2.dev.andrewmercer.com.
7.13.10.10.in-addr.arpa. IN PTR ns3.dev.andrewmercer.com.
8.13.10.10.in-addr.arpa. IN PTR ns4.dev.andrewmercer.com.
/etc/bind/named.conf.local
include "/etc/bind/zones.rfc1918";
zone "dev.andrewmercer.com" {
type master;
file "/etc/bind/db.dev.andrewmercer.com";
allow-update { trusted; };
};
zone "13.10.10.in-addr.arpa" {
type master;
file "/etc/bind/db.13.10.10";
allow-update { trusted; };
};
Cutover checklist¶
- Write the new zone files above; bump the Serial on both.
named-checkzone dev.andrewmercer.com /etc/bind/db.dev.andrewmercer.comand the same for the reverse zone.- Swap the
zoneblocks innamed.conf.local— remove the oldandrewmercer.localblock once the new one is confirmed loading (rndc reload, thendig @10.10.13.5 dev.andrewmercer.com NS). - If
andrewmercer.comis a real registered domain with its own external DNS provider, delegatedev.andrewmercer.comthere: add NS records forns1–ns4.dev.andrewmercer.comat the parent, plus glue A records for them (required since these name servers' own names live inside the zone they serve). - Update
/etc/resolv.conf/ DHCP options on LAN clients if the internal search domain changes. - Keep the old
andrewmercer.localzone resolvable for a while (or at least log queries against it) to catch anything still hardcoded to the old name before fully retiring it.
Troubleshooting¶
isc_stdio_open '/etc/bind/named.log' failed: permission denied
The log path in named.conf.logging isn't writable by the bind user. Confirm the log file's actual configured location matches where you've set permissions — in this setup it should be under /var/log/named/, owned bind:bind:
sudo mkdir -p /var/log/named
sudo chown bind:bind /var/log/named
sudo systemctl restart bind9
rndc: connect failed: 127.0.0.1#953: connection refused
named isn't running, or crashed on startup (often because of the log permission issue above — check journalctl -u bind9 first). Confirm with systemctl status bind9, fix the underlying cause, then restart.
References¶
- https://www.isc.org/bind
- https://en.wikipedia.org/wiki/BIND
- https://bind9.readthedocs.io/en/latest/dnssec-guide.html