1. What BIND Is¶
BIND (Berkeley Internet Name Domain) is the most widely deployed DNS server software in existence, developed originally at UC Berkeley in the early 1980s and now maintained by the Internet Systems Consortium (ISC). It can act as an authoritative server, a recursive resolver, or both simultaneously, and it's the reference implementation many other DNS software packages are tested against.
The core daemon is named ("name-dee," the "d" suffix marking it as a Unix daemon). Administration happens through:
- Configuration files —
named.confand included files, defining options, zones, ACLs, logging, and views. - Zone files — the actual resource record data for each zone.
rndc(Remote Name Daemon Control) — a control utility for reloading, reconfiguring, and querying a runningnamedprocess without restarting it.
2. Installation¶
Debian / Ubuntu¶
sudo apt update
sudo apt install -y bind9 bind9utils bind9-doc
sudo systemctl enable --now bind9.service
(aptitude works identically to apt for this; apt is the more common modern choice.) Debian/Ubuntu packages configuration under /etc/bind/, run as user bind, and cache/working data under /var/cache/bind/.
RHEL / CentOS / Rocky / Alma¶
sudo yum install -y bind bind-utils
sudo systemctl enable --now named.service
RHEL-family systems configure under /etc/named.conf and /etc/named/, run as user named, and store zone data under /var/named/.
A chrooted variant is also available on RHEL-family systems:
sudo yum install -y bind bind-utils bind-chroot
sudo systemctl enable --now named-chroot.service
This confines named's filesystem view to /var/named/chroot/, so all the paths referenced in named.conf (zone files, keys, PID file) are interpreted relative to that jail. See §9 for the full chroot procedure and why it matters.
3. Configuration File Structure¶
named.conf is rarely one monolithic file in practice — it's conventionally split by concern and stitched together with include statements, which keeps options, logging, and zone definitions easy to manage independently:
# /etc/bind/named.conf (Debian/Ubuntu layout)
include "/etc/bind/named.conf.logging";
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
RHEL-family systems typically keep everything in one /etc/named.conf, with include "/etc/named.rfc1912.zones"; pulling in standard localhost/loopback zone definitions.
3.1 options { } — global server behavior¶
This block controls how named behaves as a whole: what it listens on, whether it recurses, and who it will talk to.
options {
directory "/var/cache/bind";
listen-on { 10.10.13.5; };
listen-on-v6 { any; };
recursion yes;
allow-recursion { trusted; };
allow-transfer { trusted; };
dnssec-validation auto;
auth-nxdomain no;
};
Key directives:
| Directive | Purpose |
|---|---|
directory |
Working directory named uses to resolve relative paths (zone files, etc.) |
listen-on / listen-on-v6 |
Interfaces/addresses to bind on for IPv4/IPv6 |
recursion |
Whether this server will perform recursive lookups on behalf of clients |
allow-recursion |
Who is permitted to ask for recursive resolution — critical to restrict, since an open recursive resolver is an abuse vector (amplification attacks) |
allow-query |
Who may query this server at all (for any answer, recursive or authoritative) |
allow-transfer |
Who may pull a full zone transfer (AXFR/IXFR) — should be restricted to known secondaries |
allow-update |
Who may perform dynamic updates (RFC 2136) against a zone |
forwarders |
Upstream resolvers to forward unresolved queries to, instead of doing iterative resolution from the root |
dnssec-validation |
Whether/how to validate DNSSEC signatures on answers this server receives (auto uses the built-in root trust anchor) |
auth-nxdomain |
Whether to mark this server's NXDOMAIN responses as authoritative; no is the RFC 1035–conformant setting |
3.2 ACLs — naming groups of addresses¶
Rather than repeating IP lists across every allow-* directive, define named access control lists once:
acl "trusted" {
10.10.13.101;
10.10.13.102;
10.10.13.103;
10.10.13.104;
};
trusted can then be referenced anywhere an address-match-list is expected (allow-recursion { trusted; };, etc.). Built-in ACLs always available include any, none, localhost (addresses of the server's own interfaces), and localnets (networks directly attached to those interfaces).
3.3 zone { } — defining what this server is authoritative for¶
zone "example.local" {
type master;
file "/etc/bind/db.example.local";
allow-update { trusted; };
};
Zone types:
| Type | Meaning |
|---|---|
master (or primary) |
This server holds the definitive, editable copy of the zone |
slave (or secondary) |
This server pulls a read-only copy from a master via zone transfer; requires masters { <ip>; }; |
forward |
Send all queries for this zone to specific forwarders rather than answering locally |
stub |
Like a slave, but only transfers the zone's NS records, not the full data — used to keep delegation info fresh without hosting the whole zone |
hint |
Special type used exactly once, for bootstrapping knowledge of the root servers |
A secondary zone looks like:
zone "example.local" {
type slave;
masters { 10.10.13.5; };
file "/var/cache/bind/db.example.local.secondary";
};
3.4 Logging¶
BIND's logging is defined in two parts: channels (where logs go and how) and categories (which kinds of events get sent to which channels).
logging {
channel "debug" {
file "/var/log/named/named.log" versions 2 size 50m;
print-time yes;
print-category yes;
};
category "queries" { "debug"; };
category "security" { "debug"; };
category "config" { "debug"; };
};
Available channel destinations besides file include syslog (send to the system logger, with a facility) and stderr. versions and size together implement log rotation internal to named itself — versions 2 size 50m keeps two 50MB rotated files.
Important operational note: logging every category (queries, client, network, dispatch, etc.) at debug severity, as shown above, is extremely verbose and appropriate only for active troubleshooting. Left enabled permanently, queries logging in particular records every single lookup any client makes — a meaningful privacy and disk-usage consideration — and should be scoped down (or removed) once the immediate problem is resolved. A more sustainable steady-state logging configuration separates severities properly:
logging {
channel "default_log" {
file "/var/log/named/named.log" versions 5 size 20m;
severity notice;
print-time yes;
print-severity 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 dnssec { default_log; };
category lame-servers { null; }; # noisy, low-value in most setups
};
The log directory's ownership must match the user named runs as, or the daemon will fail to open its log file at startup:
# Debian/Ubuntu
sudo mkdir -p /var/log/named && sudo chown bind:bind /var/log/named
# RHEL-family
sudo mkdir -p /var/log/named && sudo chown named:named /var/log/named
3.5 Views (split-horizon DNS)¶
Where internal and external clients need different answers for the same zone, named supports views — each view has its own match-clients selector and its own complete set of zone definitions, evaluated top to bottom, first match wins:
view "internal" {
match-clients { 10.10.13.0/24; };
recursion yes;
zone "example.com" {
type master;
file "/etc/bind/internal/db.example.com";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "example.com" {
type master;
file "/etc/bind/external/db.example.com";
};
};
Once views are in use, every zone must be defined inside a view (you can't mix top-level zones and views in the same config).
4. Zone File Anatomy¶
$TTL 604800
@ IN SOA ns1.example.local. admin.example.local. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative cache TTL
IN NS ns1.example.local.
IN NS ns2.example.local.
ns1 IN A 10.10.13.5
ns2 IN A 10.10.13.6
www IN A 10.10.13.10
mail IN A 10.10.13.11
IN MX 10 mail.example.local.
$TTLsets the default TTL applied to any record that doesn't specify its own.@is shorthand for "the zone's own origin" (the name given in the enclosingzoneblock).- Unqualified names (
www,ns1) are automatically appended with the zone origin —wwwbecomeswww.example.local.A trailing dot on a name means "this is already fully qualified, don't append the origin"; omitting it when you meant a fully qualified name is one of the single most common zone-file mistakes (e.g. writingns1.example.localwithout the final dot silently producesns1.example.local.example.local.). - Every time a zone file is edited by hand, the Serial must be incremented, or secondaries and caching resolvers won't recognize that anything changed and will keep serving stale data until their own cache/refresh timers happen to expire.
Reverse zones¶
Reverse lookups (address → name) live in a parallel tree under in-addr.arpa. for IPv4 (octets reversed) and ip6.arpa. for IPv6 (nibbles reversed):
$TTL 604800
@ IN SOA ns1.example.local. admin.example.local. (
2 604800 86400 2419200 604800 )
IN NS ns1.example.local.
5 IN PTR ns1.example.local.
10 IN PTR www.example.local.
referenced from named.conf with a zone name matching the network in reverse-octet form, e.g. zone "13.10.10.in-addr.arpa" { ... }; for the 10.10.13.0/24 network.
CNAME caveat¶
A name with a CNAME record cannot have any other record type at that same name (no A record alongside a CNAME, for instance) — this is a hard protocol rule (RFC 1034 §3.6.2), not a BIND-specific restriction, and violating it produces undefined/inconsistent behavior across resolvers.
5. Operational Commands¶
# Validate configuration syntax before reloading
named-checkconf /etc/bind/named.conf
# Validate a specific zone file's syntax
named-checkzone example.local /etc/bind/db.example.local
# Reload all zones without restarting the process (re-reads named.conf too)
sudo rndc reload
# Reload just one zone
sudo rndc reload example.local
# Force a specific secondary to refresh a zone from its master immediately
sudo rndc retransfer example.local
# Query the running server's status
sudo rndc status
# Flush the resolver's cache (useful after testing recursive behavior)
sudo rndc flush
# Query a name against a specific server directly
dig @10.10.13.5 www.example.local
# Trace the full delegation path for a name (root -> TLD -> authoritative)
dig +trace example.local
Always run named-checkconf and named-checkzone before reloading in any environment that matters — a syntax error in a zone file will cause named to skip loading that zone (logged, but easy to miss) rather than crash outright, silently leaving stale or absent data being served.
6. rndc Setup¶
rndc authenticates to named using a shared key, generated automatically on most distro packages and stored in /etc/bind/rndc.key (Debian/Ubuntu) or /etc/rndc.key (RHEL-family). If rndc fails with a connection error:
rndc: connect failed: 127.0.0.1#953: connection refused
check that:
1. named is actually running (systemctl status named / bind9).
2. named.conf includes a controls { } clause (or accepts the package default) listening on 127.0.0.1:953.
3. The key referenced in /etc/rndc.conf matches the key named itself is using — a key mismatch produces a less obvious "connection refused"-style failure rather than an explicit auth error on older versions.
7. Security Hardening¶
- Disable recursion on authoritative-only servers. If a server's job is to answer for zones it hosts, set
recursion no;— this closes off the amplification-attack surface entirely for that server. - Restrict
allow-transfer. Public, unrestricted AXFR hands your entire zone inventory to anyone who asks (dig axfr example.com @ns1.example.comfrom any host). Restrict to your actual secondaries by IP, and preferably pair with TSIG. - Restrict
allow-recursion/allow-query-cache. A recursive resolver reachable by the whole internet is an open resolver — a well-known DDoS amplification vector. Scope it to your own networks. - Use TSIG for zone transfers and dynamic updates, not just IP-based ACLs — IP addresses can be spoofed, especially over UDP.
- Run
namedin a chroot on servers where an unpatched vulnerability escalating to full filesystem access is an unacceptable risk (see §9). - Keep
dnssec-validationenabled (autois the sane default on modern BIND) so this server itself doesn't blindly trust unsigned or tampered upstream answers when acting as a resolver. - Run current BIND releases. ISC publishes CVEs for
namedperiodically (crashes from malformed packets, cache poisoning variants); the software's ubiquity makes it a well-studied attack target, so patching cadence matters more here than for more obscure services.
8. Troubleshooting Reference¶
| Symptom | Likely cause / fix |
|---|---|
isc_stdio_open '.../named.log' failed: permission denied |
Log directory not owned by the user named runs as (bind on Debian, named on RHEL) — chown it. |
rndc: connect failed: 127.0.0.1#953: connection refused |
named isn't running, or its controls clause / rndc key doesn't match — see §6. |
| Zone silently not answering | Check named-checkzone output and journalctl -u named / /var/log/named/* — a malformed zone file is skipped, not fatal, and easy to miss without checking logs. |
| Changes to a zone not appearing anywhere | SOA Serial wasn't incremented, so secondaries/resolvers see no reason to refetch. |
SERVFAIL on every query for a signed zone |
Likely a DNSSEC validation failure — check dig +dnssec output and system clock (signature validity windows are time-based; clock drift breaks validation). |
| External clients get an internal address (or vice versa) | Views misconfigured, or a query matched the wrong view's match-clients due to view ordering (first match wins). |
| Secondary never picks up changes | Master's allow-transfer excludes the secondary's IP, or a firewall blocks TCP/53 (zone transfers use TCP, not just UDP). |
9. Running named in a chroot Jail (RHEL-family, bind-chroot)¶
A chroot confines the daemon's view of the filesystem to a subtree, so that even a serious vulnerability in named that achieves arbitrary file access is contained to that subtree rather than the whole host. On RHEL-family systems this is a supported, packaged option (bind-chroot) that relocates the effective root to /var/named/chroot/.
sudo yum install -y bind bind-utils bind-chroot
After installation, all paths referenced anywhere in named.conf are relative to /var/named/chroot/ — so directory "/var/named"; in the config actually resolves on disk to /var/named/chroot/var/named/. The package typically bind-mounts or symlinks the relevant subtrees into place automatically; the manual permissions pass (needed if setting this up by hand, or auditing an existing setup) is:
chown root:named /var/named/chroot/
chown -R root:named /var/named/chroot/etc
find /var/named/chroot/etc -type f -exec chmod 0640 {} \;
find /var/named/chroot/etc -type d -exec chmod 0750 {} \;
chown root:named /var/named/chroot/var
chown named:named /var/named/chroot/var/log
chown named:named /var/named/chroot/var/tmp
chown root:named /var/named/chroot/var/named
chown -R named:named /var/named/chroot/var/named/data
The pattern: configuration under etc/ is root-owned but group-readable by named (0640/0750), while directories named needs to actively write to at runtime (var/log, var/tmp, var/named/data) are owned by named directly. Enable the chroot-aware service unit rather than the plain one:
systemctl enable --now named-chroot.service
Since the chroot changes every effective path, named-checkconf/named-checkzone should be pointed at the paths as they exist inside the jail (or run from within it) to validate accurately — checking the un-chrooted path outside the jail will simply report the files as missing.
10. Migrating/Restructuring a Zone (e.g., Domain → Subdomain)¶
Converting example.local into a subdomain of a larger namespace (e.g., dev.example.com) is mechanically a rename plus a delegation change, not a special zone type:
- Create the new zone file with the new origin, updating the SOA's MNAME (primary server) and all NS/A records to the new fully-qualified names.
- Create/update the corresponding reverse zone's SOA and NS records to match the new names (the PTR targets change even though the IP-to-arpa mapping doesn't).
- Add the new
zoneblock tonamed.conf.local(or equivalent) pointing at the new file, and remove the old zone block once cut over. - Increment the Serial on both the forward and reverse zone.
- If this zone is delegated from a parent (e.g.,
example.com's own DNS provider needs to know aboutdev.example.com), update the delegation (NS records, plus glue if the new name servers live insidedev.example.comitself) at the parent. - Update any resolver/client configuration (
/etc/resolv.conf, DHCP options) and any hardcoded references to the old zone name in other services. - Verify with
dig +traceagainst the new name before decommissioning the old zone, and keep the old zone's records resolvable (even if just as a CNAME chain to the new names) for the duration of the old TTLs still cached out in the wild.
11. Reference Links¶
- ISC BIND homepage: https://www.isc.org/bind
- BIND 9 Administrator Reference Manual: https://bind9.readthedocs.io/
- BIND DNSSEC Guide: https://bind9.readthedocs.io/en/latest/dnssec-guide.html
- Wikipedia overview: https://en.wikipedia.org/wiki/BIND