A DNS zone file is where the mapping from names to addresses actually lives. Anyone running a domain needs a working understanding of it — not because it changes daily, but because a mistake there makes the whole service invisible.
The anatomy of a zone
; /etc/bind/zones/example.org.zone
; WARNING: records that should be absolute end with a dot!
; Without the dot the zone name is appended automatically.
$TTL 3600
$ORIGIN example.org.
@ IN SOA ns1.example.org. hostmaster.example.org. (
2026092601 ; Serial - increment on EVERY change!
3600 ; Refresh - how often secondaries ask
900 ; Retry - wait after a failed attempt
1209600 ; Expire - when the zone becomes invalid
300 ) ; Minimum/negative TTL
; Name servers
@ IN NS ns1.example.org.
@ IN NS ns2.example.org.
; Addresses
@ IN A 203.0.113.10
ns1 IN A 203.0.113.11
ns2 IN A 198.51.100.11
www IN A 203.0.113.10
mail IN A 203.0.113.20
api IN A 203.0.113.30
; IPv6
www IN AAAA 2001:db8::10
; Aliases (CNAME) - must point at a NAME, never at an A/AAAA record
www2 IN CNAME www
ftp IN CNAME www
; A CNAME must NOT exist at the zone apex (@)!
; SOA and NS are required there, and a CNAME
; forbids any other record on the same name.
; Mail
@ IN MX 10 mail.example.org.
@ IN MX 20 backup-mail.example.net.
; The number is the preference - lower is preferred.
; The MX value MUST be a name, not an address!
; Text records
@ IN TXT "v=spf1 mx a:mail.example.org -all"
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:This email address is being protected from spambots. You need JavaScript enabled to view it. "
default._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."
; Wildcard - handle with care!
*.dev IN A 203.0.113.40
; Catches ALL undefined names under dev.example.org.
; Convenient, but it hides typos: "apti.exmaple.org"
; instead of "api" might suddenly point at a test server.
; Service records
_sip._tcp IN SRV 10 60 5060 sipserver.example.org.
; Format: priority weight port target
The record types that matter
- A / AAAA: name to IPv4 or IPv6 address.
- CNAME: alias to another name. Never at the apex, never alongside other records of the same name.
- MX: mail delivery target, with priority.
- TXT: free text — used for SPF, DKIM, DMARC and domain verification.
- NS: which name servers are authoritative for this zone.
- SOA: zone administration data — exactly one per zone.
- SRV: service, protocol, port, target.
- PTR: reverse lookup, lives in a separate zone (
in-addr.arpa), maintained by your provider. - CAA: which certificate authorities may issue certificates for the domain. An effective measure against unauthorised issuance.
@ IN CAA 0 issue "letsencrypt.org" @ IN CAA 0 iodef "mailto:This email address is being protected from spambots. You need JavaScript enabled to view it. "
The serial number mistake
This is the most common error in zone operations: if the serial is not incremented, secondary name servers never pick up the change. They plausibly believe they are current — and keep serving stale data.
# Convention: YYYYMMDDxx (2026092601 = 2026-09-26, first change)
# Advantage: human-readable and easy to compare.
# Procedure after every change
named-checkzone example.org /etc/bind/zones/example.org.zone
rndc reload example.org
# Check on the SECONDARY, not the primary!
dig @ns2.example.org example.org SOA +short | awk '{print $3}'
dig @ns2.example.org www.example.org A +short
# If the serial differs, the zone was not transferred.
# Log: journalctl -u named | grep -i 'transfer\|xfer'
Verification: the full sequence
# 1. Syntax check - BEFORE loading
named-checkzone example.org /etc/bind/zones/example.org.zone
# Success output: "example.org.zone: loaded serial 2026092601"
# 2. Config check
named-checkconf
named-checkconf -z # load and verify all zones
# 3. Load
rndc reload
journalctl -u named -n 30 --no-pager
# 4. Query the authoritative server directly (+norecursion!)
dig @ns1.example.org example.org SOA +norecurse
dig @ns1.example.org www.example.org A +norecurse
# Without +norecurse a cached answer could be misleading.
dig @ns1.example.org example.org AXFR | head -20
# 5. Check from the outside
dig +short NS example.org
dig +short www.example.org
dig +short MX example.org
dig +short TXT example.org
# 6. Expect TTL effects
# Old records stay in resolver caches until the TTL expires.
# Therefore: lower the TTL BEFORE a migration (e.g. 5 minutes),
# migrate, wait, then raise it again.
Split horizon
# Different answers for internal and external views:
# External: www -> public address
# Internal: www -> internal address (or a different target)
# Implemented with two zone files of the same name
# on different servers (views in BIND).
# Pitfall: a name resolved differently inside must be maintained
# carefully. When the two versions diverge, troubleshooting becomes
# painful - because the symptom depends on where the query came from.
# Always test against a public resolver AND your internal server,
# never just one of them.
Operating rules
- Keep zone files in version control. Manual edits leave no trace of what changed when.
- Incrementing the serial is mandatory. Best generated automatically from the date.
- Two name servers in different networks. A second server in the same region or with the same provider does not help during a regional outage.
- Choose TTL deliberately. Short (300) for services that may move — long (86400) for stable records. Short TTLs mean more load.
- Consider DNSSEC. It prevents answer tampering but requires disciplined key management — a mistake there can make the whole domain unreachable.
- Avoid catch-all wildcards without need. A wildcard hides typos and makes unintended names resolvable.
- Verify after transfer, not after saving. The secondaries and a public resolver are the real check.
The most common errors
- Missing trailing dot.
www IN CNAME www.example.org(no dot) becomeswww.example.org.example.org. The classic. - Serial not incremented. See above.
- CNAME at the apex. Not allowed — SOA and NS live there.
- CNAME alongside other records. A name with a CNAME may have no other record.
- Unbalanced parentheses. A missing closing bracket in the SOA record makes the file run on unexpectedly, producing confusing errors.
- No syntax check before loading.
named-checkzonecosts one second and prevents outages. - Port 53 blocked. When secondaries cannot transfer, the zone is usually fine — UDP/TCP 53 is blocked. Check reachability first.
Related: DNS explained, DNSSEC signatures, DNS, DNS-Zonen (DE).