Centralized DNS is one of those services that must be always UP, predictable, and fast. When it isn’t, everything breaks in strange and unpleasant ways.
This article describes a robust central DNS architecture for Linux environments using:
- 3 authoritative DNS servers
- 1 dedicated caching resolver
- Clear separation between authoritative and recursive roles
Architecture Overview
Roles
| Server | Role | Purpose |
|---|---|---|
| dns-auth-01 | Authoritative | Primary (master) |
| dns-auth-02 | Authoritative | Secondary (slave) |
| dns-auth-03 | Authoritative | Secondary (slave) |
| dns-cache-01 | Recursive / Cache | Internal resolution |
Why Separate Roles?
Authoritative and recursive DNS have very different workloads:
- Authoritative DNS: predictable, zone-based, read-only
- Recursive DNS: bursty, cache-heavy, user-facing
Mixing them increases attack surface, complexity, and failure impact.
Software Choices
Recommended stack:
- BIND9 or NSD for authoritative servers
- Unbound for caching/recursive resolver
Reasons:
- Mature, well-understood behavior
- Clear separation of responsibilities
- Excellent Linux support
- Scriptable and observable
Network Layout
Example internal layout:
10.0.0.10 dns-auth-01 (master)
10.0.0.11 dns-auth-02 (slave)
10.0.0.12 dns-auth-03 (slave)
10.0.0.20 dns-cache-01 (recursive)
All Linux servers point only to dns-cache-01 as their resolver.
Authoritative DNS Configuration
Master Server (dns-auth-01)
Zones are managed only on the master.
Example BIND zone definition:
zone "example.internal" {
type master;
file "/etc/bind/zones/example.internal.zone";
allow-transfer { 10.0.0.11; 10.0.0.12; };
also-notify { 10.0.0.11; 10.0.0.12; };
};
Key points:
- Zone transfers restricted by IP
- NOTIFY enabled for fast propagation
- No recursion enabled
Disable recursion:
options {
recursion no;
allow-query { any; };
};
Slave Servers (dns-auth-02 / dns-auth-03)
Example configuration:
zone "example.internal" {
type slave;
masters { 10.0.0.10; };
file "/var/cache/bind/example.internal.zone";
};
Slaves:
- Never edited manually
- Automatically sync zones
- Serve as HA and load distribution
Caching Resolver (dns-cache-01)
Use Unbound as a dedicated recursive resolver.
Unbound Configuration
Minimal but effective setup:
server:
interface: 0.0.0.0
access-control: 10.0.0.0/24 allow
do-ip6: no
hide-identity: yes
hide-version: yes
prefetch: yes
cache-min-ttl: 300
cache-max-ttl: 86400
Forward Internal Zones to Authoritative Servers
forward-zone:
name: "example.internal"
forward-addr: 10.0.0.10
forward-addr: 10.0.0.11
forward-addr: 10.0.0.12
External Resolution
Either:
- Use root hints (recommended for independence)
- Or forward to trusted upstream resolvers
Example:
forward-zone:
name: "."
forward-addr: 9.9.9.9
forward-addr: 1.1.1.1
Client Configuration
All Linux servers use the caching resolver only:
/etc/resolv.conf
nameserver 10.0.0.20
Or via systemd-resolved:
# resolvectl dns eth0 10.0.0.20
Clients never query authoritative servers directly.
High Availability Considerations
Resolver Redundancy
For production environments:
- Deploy two caching resolvers
- Use DHCP or systemd-resolved fallback ordering
Example:
nameserver 10.0.0.20
nameserver 10.0.0.21
Zone Management
- Store zone files in Git
- Increment SOA serials automatically
- Deploy via CI/CD or Ansible
DNS changes should be auditable, not ad-hoc.
Security Hardening
Minimum recommendations:
- No recursion on authoritative servers
- Firewall restricts TCP/UDP 53
- TSIG for zone transfers (optional but recommended)
- Disable version disclosure
- Monitor query rates
Monitoring & Validation
Useful tools:
- dig +trace
- unbound-control stats
- rndc status
- Prometheus exporters for BIND/Unbound
DNS that isn’t monitored will fail silently.
Final Thoughts
This setup scales well, is easy to reason about, and avoids the most common DNS mistakes:
- Mixing recursive and authoritative roles
- Letting clients query everything directly
- Overcomplicating zone management
DNS should be boring.
If it’s exciting, something is wrong.







