Skip to content

/commands/dns/validate-dns-zone-file

Validate a DNS zone file before loading it (named-checkzone, dnspython)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
DNS
versions
BIND 9, dnspython 2.x
os
any

To check a zone file, run named-checkzone ZONE FILE (from BIND 9, packaged as bind9-utils or bind-utils): it performs the same checks named does when loading the zone and returns exit status 1 if it found errors, 0 otherwise. Without BIND, dns.zone.from_file() in dnspython parses the file and raises an exception that names the problem and the line, which works on any OS with Python.

named-checkzone

bash
named-checkzone example.com /etc/bind/db.example.com
named-checkzone -q example.com db.example.com && echo valid     # exit code only
named-checkzone -o - example.com db.example.com                  # print the zone as parsed

ZONE is the origin the file is loaded under, so relative names are expanded against it. -q prints nothing and only sets the exit code, which suits CI and pre-reload hooks. -o - dumps the parsed zone, which shows exactly what each relative name expanded to.

The same check with dnspython

bash
cat > check_zone.py <<'EOF'
import sys
import dns.exception, dns.zone

for path in sys.argv[1:]:
    try:
        z = dns.zone.from_file(path, origin="example.test.", relativize=False)
        z.check_origin()
        print(f"{path}: OK, {len(z.nodes)} names, serial {z.get_soa().serial}")
    except dns.exception.DNSException as e:
        print(f"{path}: {type(e).__name__}: {e}")
EOF
cat > good.zone <<'EOF'
$ORIGIN example.test.
$TTL 3600
@     IN SOA ns1 hostmaster 2026102301 7200 900 1209600 300
@     IN NS  ns1
ns1   IN A   192.0.2.53
@     IN A   192.0.2.10
www   IN CNAME @
EOF
cat > nosoa.zone <<'EOF'
$TTL 3600
@     IN NS  ns1
ns1   IN A   192.0.2.53
EOF
cat > badip.zone <<'EOF'
$TTL 3600
@     IN SOA ns1 hostmaster 1 7200 900 1209600 300
@     IN NS  ns1
ns1   IN A   192.0.2.300
EOF
cat > extra.zone <<'EOF'
$TTL 3600
@     IN SOA ns1 hostmaster 1 7200 900 1209600 300
www   IN AAAA 2001:db8::1 extra
EOF
cat > cname.zone <<'EOF'
$TTL 3600
@     IN SOA ns1 hostmaster 1 7200 900 1209600 300
@     IN NS  ns1
ns1   IN A   192.0.2.53
www   IN CNAME @
www   IN A   192.0.2.11
EOF
uv run -q --no-project --python 3.13 --with dnspython==2.8.0 python check_zone.py good.zone nosoa.zone badip.zone extra.zone cname.zone

Output

good.zone: OK, 3 names, serial 2026102301
nosoa.zone: NoSOA: The DNS zone has no SOA RR at its origin.
badip.zone: SyntaxError: badip.zone:5: Text input is malformed.
extra.zone: SyntaxError: extra.zone:4: expected EOL or EOF, got 3 "extra"
cname.zone: CNAMEAndOtherData: rdataset type is not compatible with a CNAME node

In this run the line number dnspython reported was one past the faulty record (it had already read the line break), so look at that line and the one before it.

What each tool catches

Problemnamed-checkzonednspython from_file
Syntax error, bad IP address, extra fieldError, exit 1dns.exception.SyntaxError with file and line
No SOA at the originErrorNoSOA (from_file checks the origin by default)
No NS at the originErrorNoNS (raised by check_origin)
MX or NS pointing to a CNAMEWarning by default (-M, -n modes)Not checked
MX/NS/SRV target without an address recordChecked by -i full (the default)Not checked
CNAME next to other data at the same nameErrorCNAMEAndOtherData while parsing

Pitfalls

  • dnspython accepted a test file without $TTL whose records had no TTL; other software may warn or choose a different default, so set $TTL at the top of every file.
  • @ means the current origin. Inside a file loaded as example.com, @ IN MX 10 mail targets mail.example.com..
  • Running the check under the wrong zone name makes relative names expand wrongly while the file still "passes". Use the exact zone name from named.conf.

A file that passes both checks can still serve wrong data (for example a typo in an IP address that is still valid). After reloading, query the authoritative server for the changed names, as described in checking DNS propagation.

FAQ

Does named-checkzone catch a forgotten serial increment?

No. It checks one file on its own, so it cannot know the previous serial. Compare the SOA serial with the one the servers publish (dig SOA +short) before you reload, because secondaries ignore a zone whose serial did not increase.

Why does a name without a trailing dot end up as www.example.com.example.com?

Names without a final dot are relative to $ORIGIN. A CNAME or MX target written as www.example.com (no dot) becomes www.example.com.example.com.; write fully qualified targets with the trailing dot, or use the relative form on purpose.

Is it safe to run named-checkzone on a file someone sent me?

The BIND manual warns against it: $INCLUDE directives can make the parser read other files you can access, and parts of them can show up in error messages. Run it only on trusted zone text or as an unprivileged user.

Sources