
What Is a DNS Zone File, and How Do You Read One?
Every DNS dashboard, from your registrar's simple record list to a cloud provider's API, is ultimately a friendly front end for the same thing: a zone file. Sooner or later you will meet one directly, whether you are exporting records before transferring DNS to another provider, importing a BIND file into a new host, or debugging a server you inherited. Zone files look cryptic at first because they lean heavily on shorthand: missing names, missing TTLs, names with and without trailing dots. Once you know the half-dozen rules behind that shorthand, they become very easy to read. This article walks through the format piece by piece, explains each directive and field, and shows how to validate a zone before you load it. For an overview of the individual record types you will see, refer to What Are DNS Records?
What Is a DNS Zone File?
A DNS zone file is a plain-text file that describes all the resource records in one DNS zone, written in the master file format defined in RFC 1035, section 5. Authoritative servers such as BIND, NSD and Knot load these files to answer queries. Managed DNS providers usually store records in a database, but nearly all of them can import and export this format, which makes it the common language of DNS.
A zone file covers one zone, such as example.com, not necessarily one domain. Subdomains that have been delegated elsewhere live in their own zone files. That distinction is explained in What Is the Difference Between a DNS Zone and a Domain?
A Complete Example
Here is a small but realistic zone file. We will take it apart section by section.
; Zone file for example.com
$ORIGIN example.com.
$TTL 3600
@ IN SOA ns1.example.com. hostmaster.example.com. (
2026100101 ; serial
7200 ; refresh (2 hours)
900 ; retry (15 minutes)
1209600 ; expire (2 weeks)
300 ) ; negative caching TTL (5 minutes)
; Nameservers
IN NS ns1.example.com.
IN NS ns2.example-dns.net.
; Apex addresses
IN A 203.0.113.10
IN AAAA 2001:db8::10
; Mail
IN MX 10 mail.example.com.
IN MX 20 backup-mx.example-mail.net.
IN TXT "v=spf1 mx -all"
; Hosts
ns1 IN A 203.0.113.53
mail IN A 203.0.113.25
www 300 IN CNAME example.com.
api IN A 203.0.113.20
IN A 203.0.113.21
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
The Building Blocks
Comments
Everything after a semicolon (;) on a line is a comment. Comments are ignored by the server, which makes them ideal for documenting why a record exists. Many outages start with someone deleting a record nobody remembered the purpose of.
Directives
Lines starting with a dollar sign are directives that control how the rest of the file is interpreted, not records themselves.
| Directive | Defined in | What it does |
|---|---|---|
$ORIGIN | RFC 1035 | Sets the domain appended to relative names |
$TTL | RFC 2308 | Sets the default TTL for records without an explicit one |
$INCLUDE | RFC 1035 | Pulls in another file at this point |
$GENERATE | BIND extension | Creates a series of records from a pattern |
$ORIGIN example.com. means any name that does not end with a dot gets .example.com. appended. $TTL 3600 means records with no TTL of their own are cached for an hour. If you want to understand how that number affects changes, read What Is a TTL in DNS?
$GENERATE is handy for repetitive records, though it is specific to BIND and some compatible servers:
$GENERATE 1-20 host-$ IN A 198.51.100.$
This creates host-1 through host-20, pointing at 198.51.100.1 through 198.51.100.20.
The record line
Every resource record follows this shape, though several fields can be omitted:
owner-name [TTL] [class] type data
- Owner name — the name the record belongs to.
- TTL — how long resolvers may cache it, in seconds.
- Class — almost always
IN(Internet). - Type —
A,AAAA,MX,CNAME,TXTand so on. - Data (RDATA) — the value, whose format depends on the type.
TTL and class can appear in either order, and both can be left out. Take www 300 IN CNAME example.com. from the example: the owner is www, the TTL is 300 seconds (overriding the $TTL default), the class is IN, the type is CNAME and the data is example.com.
The Shorthand Rules That Confuse Everyone
1. The at sign means the origin
@ stands for the current $ORIGIN. In our file, @ is example.com., so the SOA record belongs to the apex.
2. A blank owner repeats the previous one
If a line starts with whitespace, the record has the same owner as the line above. That is why the NS, A, AAAA, MX and TXT lines under the SOA all belong to example.com. even though no name appears, and why the second api line adds a second A record to api.example.com. This rule is the single most common source of misreading: an accidental leading space attaches a record to the wrong name.
3. The trailing dot makes a name absolute
A name ending in a dot is fully qualified and used exactly as written. A name without a trailing dot is relative and gets the origin appended. The classic mistake looks like this:
www IN CNAME example.com
Without the trailing dot, this means www.example.com. CNAME example.com.example.com., which almost certainly does not exist. The same mistake in an MX or NS record silently breaks mail or delegation. If the idea of a fully qualified name is new, see What Is a Fully Qualified Domain Name?
4. Parentheses allow multi-line records
Parentheses let a record span several lines, which is how the SOA record is usually written for readability. The newlines inside the parentheses are ignored.
5. Missing TTLs inherit
A record without a TTL uses the $TTL value. If there is no $TTL either, older servers fell back to the SOA minimum field, but modern practice (RFC 2308) is to always set $TTL explicitly.
6. Quotes group text
TXT data with spaces must be wrapped in double quotes. A single TXT string is limited to 255 bytes; longer values such as large DKIM keys are written as several quoted strings in a row, which clients join together.
selector1._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA" )
Reading the SOA Record
The SOA (Start of Authority) record always comes first and describes the zone itself. In our example:
ns1.example.com.is the primary nameserver.hostmaster.example.com.is the contact email, with the first dot standing in for the@, so it meanshostmaster@example.com.- The five numbers are the serial, refresh, retry, expire and negative caching TTL.
The serial is the most operationally important. Secondaries only fetch an updated copy of the zone if the serial increases, which is why the date-based YYYYMMDDnn format is common. Each field is covered in detail in What Is an SOA Record?, and how secondaries use the serial is explained in What Is a DNS Zone Transfer?
Expanding the Shorthand
A good way to check your reading is to have a tool print the zone in fully expanded form. BIND's named-compilezone does exactly that:
named-compilezone -f text -F text -o - example.com db.example.com
The -o - flag writes output to standard output. Every record comes out with its full owner name, explicit TTL and class (exact ordering and spacing vary slightly between BIND versions):
example.com. 3600 IN SOA ns1.example.com. hostmaster.example.com. 2026100101 7200 900 1209600 300
example.com. 3600 IN NS ns1.example.com.
example.com. 3600 IN NS ns2.example-dns.net.
example.com. 3600 IN A 203.0.113.10
example.com. 3600 IN MX 10 mail.example.com.
example.com. 3600 IN MX 20 backup-mx.example-mail.net.
example.com. 3600 IN TXT "v=spf1 mx -all"
example.com. 3600 IN AAAA 2001:db8::10
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
api.example.com. 3600 IN A 203.0.113.20
api.example.com. 3600 IN A 203.0.113.21
mail.example.com. 3600 IN A 203.0.113.25
ns1.example.com. 3600 IN A 203.0.113.53
www.example.com. 300 IN CNAME example.com.
Comparing this with the original makes the inheritance rules obvious. You can do the same in Python with dnspython:
import dns.zone
import dns.rdatatype
zone = dns.zone.from_file("db.example.com", origin="example.com", relativize=False)
for name, ttl, rdata in zone.iterate_rdatas():
print(f"{name.to_text():30} {ttl:>6} IN {dns.rdatatype.to_text(rdata.rdtype):6} {rdata.to_text()}")
dns.zone.from_file parses the file with the same rules a nameserver uses, and iterate_rdatas yields every record with its absolute name and resolved TTL. It raises an exception if the file has syntax errors, which makes it useful in CI checks.
Validating a Zone File Before Loading It
Never load an edited zone file without checking it first. With BIND:
named-checkzone example.com /etc/bind/db.example.com
zone example.com/IN: loaded serial 2026100101
OK
named-checkzone parses the file and reports syntax errors, out-of-zone data, CNAMEs that conflict with other records, and MX or NS targets that point at CNAMEs. Once it passes, reload just that zone:
sudo rndc reload example.com
This tells the running server to re-read the file for example.com without restarting. If you are setting up a server from scratch, How to Run Your Own DNS Server with BIND covers the full configuration.
Exporting and Importing Zone Files
Most managed DNS providers let you export your records as a BIND-format zone file and import one. A few practical points:
- Provider-only features disappear. ALIAS records, proxied flags, health-checked records and weighted routing have no standard representation. Exports may flatten them to plain records or omit them.
- SOA and NS are often replaced. When you import, the new provider usually substitutes its own SOA and NS records.
- TTLs may be normalised. Some providers enforce minimum TTLs on import.
- Check relative names. If an export uses relative names without a
$ORIGIN, the importer must be told the zone name.
Always diff the expanded output of the old and new zone after a migration.
DNS Zone File FAQ
It is plain text in the master file format defined in RFC 1035 section 5, often called BIND format. Most DNS servers and providers can read and write it.
It stands for the current origin, usually the zone's own name. In the example.com zone, @ means example.com.
A trailing dot marks a fully qualified name that is used as written. Names without a trailing dot are relative, and the origin is appended to them.
The record inherits the owner name from the previous record. This is intentional shorthand, but an accidental leading space can attach a record to the wrong name.
It sets the default time to live for any record in the file that does not specify its own TTL. It was standardised in RFC 2308.
Run named-checkzone with the zone name and file path. It reports syntax errors and common logical mistakes and prints OK if the file is valid.
Yes, if you have secondary nameservers. They only fetch a new copy when the serial increases. Many people use a date-based serial such as 2026100101.
Usually not directly, but most providers let you import and export zone files. Provider-specific record types may not survive the round trip.
Conclusion
A DNS zone file is simply a list of resource records with a few rules that keep it short: $ORIGIN and $TTL set defaults, @ stands for the origin, a blank owner repeats the previous name, a trailing dot makes a name absolute, and parentheses let long records span lines. Once you know those rules, any zone file reads like a table of names, types and values.
The habits that matter most are equally simple: always use trailing dots on fully qualified targets, increment the serial every time you edit, validate with named-checkzone before reloading, and when in doubt, expand the file with named-compilezone or dnspython to see exactly what the server will serve.
Here are some useful references for going deeper on DNS zone files:
- RFC 1035: Domain Names - Implementation and Specification — section 5 defines the master file format.
- RFC 2308: Negative Caching of DNS Queries — introduces the
$TTLdirective and redefines the SOA minimum field. - BIND 9 Documentation: BIND 9 Administrator Reference Manual — zone file syntax,
$GENERATE, and thenamed-checkzoneandnamed-compilezonetools. - dnspython Documentation: dnspython — the Python library used above to parse zone files.
- Cloudflare Learning Center: What is a DNS zone? — a short explainer of zones and zone files.


