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

  1. Keep zone files in version control. Manual edits leave no trace of what changed when.
  2. Incrementing the serial is mandatory. Best generated automatically from the date.
  3. 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.
  4. Choose TTL deliberately. Short (300) for services that may move — long (86400) for stable records. Short TTLs mean more load.
  5. Consider DNSSEC. It prevents answer tampering but requires disciplined key management — a mistake there can make the whole domain unreachable.
  6. Avoid catch-all wildcards without need. A wildcard hides typos and makes unintended names resolvable.
  7. Verify after transfer, not after saving. The secondaries and a public resolver are the real check.

The most common errors

  1. Missing trailing dot. www IN CNAME www.example.org (no dot) becomes www.example.org.example.org. The classic.
  2. Serial not incremented. See above.
  3. CNAME at the apex. Not allowed — SOA and NS live there.
  4. CNAME alongside other records. A name with a CNAME may have no other record.
  5. Unbalanced parentheses. A missing closing bracket in the SOA record makes the file run on unexpectedly, producing confusing errors.
  6. No syntax check before loading. named-checkzone costs one second and prevents outages.
  7. 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).