Enlarge
/
What does this stream of binary digits have to do with DNS? Nothing, really—but good luck finding a pretty pic somewhere that does!
Santo Heston
reader comments
121
with 76 posters participating, including story author
Share this story
Share on Facebook
Share on Twitter
Share on Reddit
If you want to be a sysadmin or network administrator of any kind, there's a fundamental technology you really need to understand—DNS, the Domain Name System. There was a time when a sysadmin with no aspirations to managing Internet-accessible services might have gotten by without understanding DNS, but that time is long, long gone.
You can't learn everything there is to know about DNS in a single article. But that's not what we're looking to do today; instead, we want to give you a clear, concise guide to the structure and meaning of the most important part of the Domain Name System: a zone file, as seen in BIND, the Berkeley Internet Name Daemon.
Sample zone file
Enlarge
/
This sample zone file doesn't have every possible record type in it—but it's a good start.
Jim Salter
Origin and TTL
Above, we have a small but complete example of a typical zone file—in fact, it's an anonymized version of a production zone file on a domain I manage. Let's go through it line by line.
$ORIGIN tld.$TTL 5m
Whenever you see an
$ORIGIN
line in a zone file, this is a shortcut that lets BIND know that any unterminated hostname references following that line should be presumed to end in the argument following
$ORIGIN
. In this case, that's
.tld
—the fictional Top Level Domain for
example.tld
.
The next line,
$TTL 5m
, declares that following lines will have a Time To Live of five minutes. This relatively short value means that remote DNS resolvers should only cache records retrieved from this zone for five minutes before requesting them again. If you're relatively certain that your DNS for a given domain won't change very often, you might consider increasing that value in order to reduce the number of times remote hosts must query your nameserver—but keep in mind that a longer TTL also means longer periods of downtime, when you must make a change to your DNS (or make a change that accidentally breaks it).
Both
$ORIGIN
and
$TTL
can be defined multiple times in the same zone—each time you redefine them, you change their value for any lines beneath the new values in the same zone file.
SOA record
example IN SOA ns1.example.tld. hostmaster.example.tld. ( 90 ; serial 4h ; refresh 15m ; retry 8h ; expire 4m) ; negative caching TTL
The first actual record in our sample zone file—or in any normal zone file—is the SOA record, which tells us the Start Of Authority for the domain. It's also easily the most confusing record type in the entire DNS system.
For any record listing, including this SOA record, the first argument is the hostname the record applies to—in this case,
example
. Remember how we set
$ORIGIN tld
on the first line of the zone file? That means that this unterminated hostname
example
expands to
example.tld
—so, we're defining the SOA for the FQDN (fully qualified domain name)
example.tld.
We're referring to this hostname
example
as "unterminated" because it doesn't end in a dot. If we wanted to bypass the
$ORIGIN
setting and refer to a FQDN directly, we'd terminate it with a final dot—eg,
example.tld.
would be the FQDN here,
with
the trailing dot.
The next argument we see is
IN
, short for "Internet." This is the record class. There are other DNS record classes, but you can easily go your entire career without seeing one of them (such as
CH
, for Chaos) in production. The record class is optional; if omitted, BIND will assume that the record being specified is of class
IN
. We recommend
not
omitting it, however, lest something change and all your zone files suddenly be broken after a BIND update!
The next two arguments are FQDNs—at least, they look like it. The first FQDN really is an FQDN, and it should be the FQDN of the primary name server for the domain itself—in this case,
ns1.example.tld.
Note that you
can
use unterminated hostnames here—for example, we could have just used
ns1.example
for this argument, which would have expanded to
ns1.example.tld.
thanks to our
$ORIGIN .tld
line—but it's probably best to be explicit here.
The second FQDN,
hostmaster.example.tld.
, isn't actually an FQDN at all. Instead, it's a perverse way of rewriting an email address.
@
is a reserved character in zone files, and the original BIND uses the first section of this "FQDN" as the user portion of an email address—so, this would translate to the address
hostmaster@example.tld
. It's
incredibly
common to see this screwed up in real-life zone files—thankfully, it doesn't much matter. We're not aware of literally anyone who actually uses this feature of a DNS zone to contact anyone.
Moving on, we have
serial
,
refresh
,
retry
,
expire
, and
negative TTL
for the zone inside parentheses. Note that the comments you see here labeling them are not required—and in real life, you'll rarely see them. We strongly prefer to put these comments in production zone files in order to make it easier to read them, but BIND itself doesn't care!
serial
—this is a simple serial number for the zone file, which must be incremented each time the contents of the zone are changed. If you don't update the zone file serial, your changes to the zone will not be picked up by DNS resolvers that have previously cached records from your zone! This used to be a YYMMDDnn format in days gone by—but that format is no longer required, or in some cases even supported. Just start your zones with serial
1
, increment to
2
the next time you make a change to the zone, and so forth.
refresh
—after this period of time, secondary nameservers should query the primary nameserver for this SOA record, to detect changes in serial number. If the serial number has incremented, any cached records must be invalidated and fetched again from the primary nameserver.
retry
—if the primary nameserver doesn't respond to an SOA request, a secondary nameserver should wait this long before attempting to query the primary nameserver again.
expire
—if the primary nameserver has failed to respond to a secondary nameserver's SOA request for this period of time, the secondary nameserver should stop offering DNS resolution for the domain entirely.
negative caching TTL
—this controls how long a "negative" response—eg, "I don't have the record you're asking for"—should be cached.
Advertisement
One of the most common areas for confusion in the SOA record is what effect the
refresh
,
retry
, and
expire
arguments have. These arguments don't affect DNS resolvers at all—only secondary authoritative nameservers for the domain. if you don't have one or more secondary nameservers for your domain, which use BIND replication to retrieve updates from the primary, these arguments won't have any effect at all.
One final note: older versions of BIND required all of these times to be in seconds... even when the actual time interval was in days, or weeks. BIND9—released almost 20 years ago, in October 2000—supports human-readable time sufffixes such as "m" for minutes, "h" for hours, and "d" for days.
Please
use these human readable suffixes when writing zone files; nobody should have to break out a calculator to figure out that 86,400 seconds is one day!
NS records
IN NS ns1.example.tld.IN NS ns2.example.tld.
In these two records, we define the hostnames, which are authoritative nameservers for our zone. Once again, we've used dot-terminated FQDNs for these records. Once again, we
could
have used unterminated hostnames—
ns1.example
and
ns2.example
—and relied on our
$ORIGIN .tld
to expand them. Doing so would make the zone more confusing and difficult to read, though.
Note that the NS record specifies
hostnames
, not IP addresses. This is a common source of confusion for DNS newbies, and it's important to get it right. You
cannot
specify a bare IP address as the nameserver for a domain; you absolutely must specify a hostname here.
Finally, note that we haven't specified the domain name itself on either line—this is because we've inherited it from the
SOA
record above. We started that line with
example
, which expands to
example.tld
. Since we haven't specified another hostname, these new
NS
records also apply to that hostname by default.
In the real world, you may also see zone files with
$ORIGIN example.tld.
, and beginning the SOA and possibly other lines with the special reserved character
@
. When you see
@
as a hostname in a zone file, that just means you're using the bare
$ORIGIN
without any further qualifiers.
MX record(s)
IN MX 10 mail.example.tld.
In this simple domain, we have a single mailserver, and it's
mail.example.tld.
The MX record just tells anyone who wants to send email to any address at
example.tld
to make their SMTP connection to the hostname specified in this record.
The preceding argument—
10
in this case—is the numeric priority of the mailserver in this specific record. Lower numbers mean higher priority. When multiple SMTP servers are available for a domain, you'll see multiple
MX
records as well, each with a different priority. In theory, higher priority mailservers should always be tried first, and lower priority mailservers only tried if the higher priority server fails.
Well-behaved SMTP servers do follow this protocol—but spammers have a tendency to deliberately target the lower-priority mailservers first, operating on the theory that high-priority servers might be anti-spam gateways, and the lowest priority servers might be the bare, unfiltered end server. Spammers suck.
A record(s)
IN A 127.0.0.1
A records are the part of a zone file that actually do what most people think of DNS as doing—they translate a hostname to a bare IPv4 address. In this case, this is a sample file only—and our
A<
/p>record for
example.tld
merely resolves to localhost, on the same principle that phone numbers in movies always start with the exchange
555
. In real life, of course, you'd put in the IP address of the server you expected to answer when you
ping example.tld
, point a Web browser to
https://example.tld/
, and so forth.
In this simple zone file, we only have a single
A
record for
example.tld
. In real life, you might encounter several—there could be multiple gateway servers capable of answering Web requests for
https://example.tld/
; and if so, each would get their own A record on their own line.
TXT record(s)
IN TXT "v=spf1 a mx a:mail.example.tld a:www.example.tld ?all"
This
TXT
, or text record, is still in the head section of our zone file, under the hostname
example.tld
. So its scope is the entire
example.tld
domain. You can put just about anything in a
TXT
record; this specific one is an
SPF
record, formatted to give mailservers information about what machines are authorized to emit mail on behalf of
example.tld
.
In this case, we're saying that we're using the SPF1 version of formatting. We then inform anyone querying this record that any valid
A
record for
example.tld
is authorized to send mail on its behalf, as is any valid
MX
for the domain, and finally that the IP addresses associated with the
A
records for
mail.example.tld
and
www.example.tld
are authorized to send mail. Finally,
?all
says that if any other machine says it wants to send mail from some address at
example.tld
, it should be allowed... but it should be examined more closely than specifically authorized hosts are.
Hostnames, subdomains, and CNAMEs beneath example.tld
$ORIGIN example.tld.ns1 IN A 127.0.0.2ns2 IN A 127.0.0.3www IN CNAME example.tld.mail IN AAAA ::1mail IN A 127.0.0.4
Now that we've defined everything we need to for the domain, we can start adding records for any hostnames and subdomains
beneath
example.tld
itself. The first thing we do here is change our
$ORIGIN
to
example.tld.
Again, notice that final terminating dot—if you forget it, things are going to get really strange and you'll tear your hair out wondering why none of your records resolve properly!
We see
A
records here for
ns1
,
ns2
, and
. These
A
records work the same way that the
A
record for the domain itself did—we are telling BIND what IP address to resolve requests for that hostname to.
We also have an
AAAA
record for
mail.example.tld.
—
AAAA
records are just like
A
records, but they're for resolving IPv6 rather than IPv4. Once again, we've chosen in our example to use a localhost address. You'll need to be familiar with
AAAA
records if you expect to set up your own mailserver—Google stopped being willing to talk to mailservers without fully working IPv6 DNS a few years ago!
Advertisement
The last record type we see here is
CNAME
, short for Canonical Name. This is an alias—it allows you to tell BIND to always resolve requests for the
CNAME
d host using the A or AAAA record specified in the target argument. In this case,
www IN CNAME example.tld.
means that the IP address for
example.tld
itself should also be handed out if somebody asks for
www.example.tld.
CNAME
records are handy, but they're a bit funky. It's worth remembering that each level of
CNAME
necessitates another DNS lookup—in this case, a remote machine that asked to resolve
www.example.tld
would be told "please look up
example.tld.
in order to find your answer," and then would need to issue a
separate
DNS request for the
A
record associated with
example.tld.
If you have
CNAME
s pointing to
CNAME
s pointing to
CNAME
s, you'll introduce unnecessary latency into requests for your resources, and your domain will appear "slow" and "laggy" to your users!
There are further limitations in
CNAME
records. Remember how we told you that
MX
and
NS
records must point to hostnames, not to raw IP addresses? More specifically, they must point directly to
A
records, not to
CNAME
records. If you try to set
MX mail.example.tld.
followed by
mail.example.tld. CNAME example.tld.
, your zone file will be broken, and
MX
lookup attempts will return errors.
Tools of the trade
If you're managing your own DNS, you'll need to be proficient in using command line tools to query your DNS server directly and see how it responds to requests—it's difficult to be certain whether the problem is DNS or something else just by putting
https://example.tld/
in a browser and seeing what happens.
dig
you@box:~$ dig @127.0.0.1 NS example.tld; <<>> DiG 9.16.1-Ubuntu <<>> NS example.tld;; global options: +cmd;; Got answer:;; ->>HEADER<
If you have access to Linux, Mac, or Windows Subsystem for Linux, by far the best command line tool is
dig
. Using
dig
is as simple as specifying a server to query, the record type you want to look for, and the FQDN it should be associated with.
In the example above, we asked the DNS server at
127.0.0.1
to show us all
NS
records associated with
example.tld
. In addition to the answers we wanted, we got a ton of diagnostic information—the DNS server we queried did not return an
ERROR
when queried, it says it is authoritative for the domain in question, and so forth.
You can also supply a
+short
argument if you want
dig
to just shut up and give you the answer you're looking for without all the verbose diagnostics:
you@box:~$ dig +short @127.0.0.1 NS example.tldns1.example.tld.ns2.example.tld.
Be aware, though, that if there aren't any answers available for a
+short
query—for example, if you typo the domain name—you won't get any response at all, even if the DNS server queried returned an error.
you@box:~$ dig +short @127.0.0.1 NS example.tmdyou@box:~$
If you want to find out
why
you didn't get an answer, you'll need to lose the
+short
argument to find out.
nslookup
If you don't have access to
dig
, you can generally get by with
nslookup
. Most commonly, this is a semi-cursed workaround for users sitting at a Windows box without access to Windows Subsystem for Linux, cygwin, or some other way to gain access to more advanced tools than the Windows CLI provides.
nslookup
is usually invoked without arguments and queried in interactive mode. Here's a sample session:
C:\> nslookup> server 127.0.0.1Default server: 127.0.0.1Address: 127.0.0.1#53> example.tldServer: 127.0.0.1Address: 127.0.0.1#53Non-authoritative answer:Name: example.tldAddress: 127.0.0.1
By setting
server 127.0.0.1
, we specified to
nslookup
to use that machine as the DNS server to query. You don't have to specify this; if you don't,
nslookup
will use whatever the default DNS resolver on your machine would.
After optionally setting the
server
, you can just type a bare hostname into
nslookup
's interactive prompt, and it will return any
A
or
AAAA
records it can find for that hostname.
If you want to query the remote server for a different type of record, you'll need to use a
set type
command.
> set type=ns> example.tldServer: 127.0.0.1Address: 127.0.0.1#53Non-authoritative answer:example.tld nameserver = ns1.example.tld.example.tld nameserver = ns2.example.tld.Authoritative answers can be found from:> set type=mx > example.tldServer: 127.0.0.1Address: 127.0.0.1#53Non-authoritative answer:example.tld mail exchanger = 10 mail.example.tld.Authoritative answers can be found from:example.tld nameserver = ns2.example.tld.example.tld nameserver = ns1.example.tld.mail.example.tld internet address = 127.0.0.4> exit
In the above examples, we used
set type=ns
and
set type=mx
to query the remote DNS server for
NS
and
MX
records for
example.tld
. It works, and you get your answers... but the syntax is fiddly, there's less diagnostic information available, it's vastly less scriptable, and if you're anything like us, you'll likely curse the antiquated thing once or twice before you're done.
The proper way to get out of
nslookup
's interactive mode is the command
exit
. Hopefully, you never need to look up information about a top-level domain also named
exit
—or if you do, you'll have a better tool available than
nslookup
when you do.
Conclusions
Hopefully, you picked up something valuable today about how DNS works and how its information is stored. Although BIND is not the only DNS server platform out there—in particular, Windows admins will need to work with Active Directory DNS—the lessons learned here apply near-equally to all platforms and applications.
Although the storage format may change somewhat from server to server—such as an Active Directory domain controller literally storing zones inside Active Directory itself, rather than a plain text file—the record types are universal, and the formatting at least near-universal.
If you're a budding sysadmin or enthusiast who's interested in running your own DNS server, I highly recommend doing it—and using the original platform when you do; BIND on either Linux or BSD. The system load of running a nameserver is nearly nonexistent at any scale short of truly massive; a $5 Digital Ocean or Linode box can handle the job just fine.
In addition to the sheer joy of learning how to manage these things, you may also find you value the ability to set your TTLs absurdly short—most managed DNS servers won't allow a TTL of less than 30m, and most will attempt to default you to TTLs of up to a week. This is fine and dandy for a DNS zone, which is already properly set up and doesn't need changing... but if your IP address changes and your DNS needs to change along with it, a five-minute TTL is a very, very fine thing to have.