Chapter 05 · Part II · Running a zone

Record types in depth

The networks site has the cheat sheet: an A record holds an address, MX names a mail server, CNAME makes an alias (networks · DHCP & DNS). This chapter is the rulebook behind that table. Why a CNAME can never sit at the top of your zone, how a client chooses among SRV records, why a TXT record is really a list of short strings, who checks CAA, what the new HTTPS record is for, and exactly when a wildcard answers and when it stays silent.

255 bytesthe most one TXT character-string can hold; longer text must be split into several strings in the same record (RFC 1035)
15 · 28 · 65the type numbers of MX, AAAA and HTTPS in the IANA registry; on the wire a type is only a 16-bit number
2023the year RFC 9460 defined SVCB and HTTPS, the first record types that tell a browser how to connect before it connects

Common mix-up: a CNAME is not a redirect. It never reaches the browser's address bar and it does not change the name the browser asks the web server for. It only tells the resolver "the records you want are stored under this other name." If you want www.example.com to send visitors to example.com, that is an HTTP redirect on a web server, not a DNS record.

Five fields, always

The anatomy of a record

Every piece of data in DNS, whatever it describes, is a resource record with the same five parts. Written in the zone-file format that RFC 1035 defined in 1987, one record looks like this:

www.example.com.   3600   IN   A   192.0.2.10
; owner name        TTL    class type  RDATA (the data itself)

The owner is the name the record belongs to. The TTL is how many seconds a resolver may keep a copy (Chapter 4 is all about it). The class is almost always IN, for internet; the others are historical. The type says how to read the last field, the RDATA. For an A record the RDATA is four bytes of IPv4 address. For an MX record it is a 16-bit number and a domain name. For a TXT record it is one or more short strings. The type decides everything about the format, and nothing else does.

On the wire the type is not a word but a 16-bit number. IANA, the registry that also hands out port numbers, keeps the list. The words you type in a zone file are only names for those numbers.

TypeNumberRDATA holdsDefined in
A1One IPv4 address (4 bytes)RFC 1035
NS2The name of an authoritative server (Chapter 6)RFC 1035
CNAME5The canonical name this name is an alias forRFC 1035, clarified by RFC 2181
MX15Preference (16 bits) and a mail server nameRFC 1035
TXT16One or more character-strings of up to 255 bytes eachRFC 1035
AAAA28One IPv6 address (16 bytes)RFC 3596
SRV33Priority, weight, port and target nameRFC 2782
DNAME39A target for a whole subtree of namesRFC 6672
SVCB64Priority, target and service parametersRFC 9460
HTTPS65SVCB, specialised for HTTPSRFC 9460
CAA257Flags, a tag and a value saying who may issue certificatesRFC 8659

The single most useful idea in this chapter is the RRset: all the records that share an owner name, a class and a type. If www.example.com has three A records, those three are one RRset. RFC 2181 made the set the unit of DNS: a server answers with the whole set or none of it, a resolver caches it as one thing, and the records in it must all carry the same TTL. Order inside a set means nothing. Exact duplicates collapse into one, because a set cannot hold the same member twice. If you ever wonder why DNS behaves the way it does, think in RRsets, not in lines of a zone file.

Go deeper: types your server has never heard of

New record types are added every few years, and older DNS software has no idea how to read their RDATA. RFC 3597, from 2003, fixed that with a generic way to write any record: the type as TYPE plus its number, and the RDATA as \#, a length in bytes, and the bytes in hexadecimal. A server can store and serve a record it does not understand, byte for byte, and a resolver can cache it, because neither needs to look inside.

; an experimental type 65400 carrying four bytes, written the RFC 3597 way
odd.example.com.  3600  IN  TYPE65400  \# 4 0A0B0C0D
; the same generic form works for a known type too: this is an ordinary A record
www.example.com.  3600  IN  TYPE1      \# 4 C000020A     ; = 192.0.2.10

RFC 3597 also says that servers must not compress names inside the RDATA of types they do not know, which is why new types define their names as uncompressed. That rule is what lets SVCB and HTTPS pass through resolvers that were written before 2023. The linter below checks that the declared length matches the hex you wrote.

Where the bytes go

A and AAAA, and why there are several

An A record holds a 32-bit IPv4 address; AAAA, defined in RFC 3596, holds a 128-bit IPv6 address. The name AAAA is a joke that stuck: four times the bits, four As. A name can have both, and on a modern network a client usually asks for both and races the connections, preferring IPv6 when it works.

A name can also have several of each, and that is the oldest form of load spreading on the internet. Because an RRset has no order, a server is free to rotate the records on each answer, so different clients try different addresses first. It is crude: DNS has no idea whether an address is overloaded or dead, and a cached answer keeps pointing at a broken machine until its TTL runs out. That is why the record types later in this chapter, SRV and HTTPS, add priorities, and why large sites steer traffic with smarter servers instead (Chapter 9).

;; ANSWER SECTION:  (simulated, documentation addresses)
www.example.com.   300  IN  A     192.0.2.10
www.example.com.   300  IN  A     192.0.2.11
www.example.com.   300  IN  AAAA  2001:db8::10
One name, one meaning

CNAME and the apex problem

A CNAME record says: this name is an alias, and every question about it should be asked about the canonical name instead. When a resolver looks up the A record of www.example.com and finds a CNAME pointing to edge.example.net, it restarts the lookup at edge.example.net and hands back both the CNAME and the final answer. Chapter 3 follows such chains hop by hop.

That redirect applies to every type at once, and that is the root of all the CNAME rules. If www could hold a CNAME and also its own MX record, which one should a mail server believe, the MX at www or the MX at the canonical name? RFC 2181, section 10.1, settles it bluntly. For any name, exactly one of these is true:

  • it has one CNAME record and nothing else (DNSSEC signatures excepted);
  • it has one or more records, none of them a CNAME;
  • it exists but holds no records at all;
  • it does not exist.

Now look at the top of a zone, the apex: the name example.com itself. The apex must hold an SOA record and NS records; that is what makes it the top of a zone (Chapter 6). Since it already holds data, it cannot also hold a CNAME. This is the famous apex problem. Point www at your hosting company with a CNAME and everything is fine. Try to do the same for example.com, which many people type without the www, and the zone is broken: some servers refuse to load it, others serve it and confuse resolvers.

Two related rules from RFC 2181 section 10.3 catch people the other way round. The name an MX or NS record points to must not be an alias. Those records trigger "additional section processing": the server adds the target's addresses to the answer so the client does not need a second query, and that shortcut does not follow CNAMEs. An MX pointing at an alias works badly at best. RFC 2782 says the same about SRV targets.

Zone lineLegal?Rule
www CNAME edge.example.net.YesAn alias below the apex, alone at its name
@ CNAME edge.example.net.NoThe apex already holds SOA and NS (RFC 2181 §10.1)
www CNAME … plus www TXT …NoA CNAME must be alone at its name
@ MX 10 mail where mail is a CNAMENoMX and NS targets must not be aliases (RFC 2181 §10.3)
@ HTTPS 0 edge.example.net.YesAliasMode is built for the apex (RFC 9460; below)

So how do so many sites put a bare example.com on a content delivery network? Most DNS providers offer a feature with a name like ALIAS, ANAME or CNAME flattening. You type something that looks like a CNAME at the apex; the provider's server looks up the target's addresses itself and answers with ordinary A and AAAA records, which are allowed at the apex. Cloudflare's documentation describes its version as following the chain and "returning the final IP address" instead of the CNAME. These are useful, but they are not DNS record types. There is no ALIAS in the IANA registry. Each provider implements its own, they do not transfer to secondary servers made by someone else, and the addresses you get depend on where the provider's server is, not where the visitor is.

Go deeper: DNAME, the alias for a whole subtree

A CNAME aliases one name. A DNAME, defined now by RFC 6672, aliases everything below a name. Put old.example.com. DNAME new.example.net. in a zone and a question for host.old.example.com is answered by rewriting it to host.new.example.net; the server sends back the DNAME and a CNAME it made up on the spot for that exact name, so even old resolvers that never heard of DNAME can follow it. The owner name itself, old.example.com, is not redirected and can keep its own records. DNAME is used for renaming a department or a reverse-DNS block without retyping every record; you will rarely need it, but now you know what it is when you see it in a dig trace.

Instrument 1

Lint a zone

Below is a small zone for example.test, written in standard zone-file format and full of the mistakes this chapter describes. A real parser reads it line by line, groups the records into RRsets, and checks each rule, citing the RFC that sets it. Edit any line and the findings update as you type. Try "Fixed version" to see the same zone with every problem repaired, or paste your own (it never leaves your browser).

Records parsed–
RRsets–
Errors–
Warnings–

    The linter understands $ORIGIN, $TTL, @, relative names, blank owners (same as the line above), quoted strings, comments and parentheses. It checks the record rules in this chapter; it does not check SOA timers or delegation, which belong to Chapter 6.

    Small print in two old types

    MX preference and the 255-byte string

    An MX record carries two things: a preference, a number from 0 to 65535, and the name of a mail server. Lower numbers are tried first. A sending server looks up every MX record for the domain, sorts by preference, and works down the list until one accepts the message; servers with equal preference should be tried in random order, which spreads the load between them. The numbers themselves mean nothing beyond their order: 10 and 20 behave exactly like 1 and 2. People leave gaps so they can slot a server in later without renumbering.

    ;; ANSWER SECTION:  (simulated)
    example.com.  3600  IN  MX  10 mx1.example.com.   ; tried first
    example.com.  3600  IN  MX  10 mx2.example.com.   ; same preference: shares the load
    example.com.  3600  IN  MX  20 backup.example.net. ; only if both above fail

    What those mail servers then do with SPF, DKIM and DMARC records is Chapter 8. The record those three live in, TXT, has one structural rule worth knowing first. A TXT record's RDATA is not one string but a list of character-strings, each with a one-byte length in front. One byte counts to 255, so no single string can be longer than 255 bytes. Anything longer, such as a 2048-bit DKIM key, has to be split into several quoted strings inside the same record:

    note.example.com.  IN  TXT  "first piece, up to 255 bytes" "second piece, up to 255 more"

    DNS itself does not join the pieces. It stores and returns them as a list, and the application decides what they mean; email's rules happen to say "glue them together with nothing in between." Two common surprises follow. A web form that lets you paste a 400-character value may split it for you, or may silently cut it, so check with dig. And two separate TXT records at the same name are two members of one RRset, in no particular order, which is a different thing from one record holding two strings.

    Services by name

    SRV: priority, weight, port, target

    MX solved "where is the mail server?" in 1987, for one service. SRV, defined in RFC 2782, solves it for any service. Its owner name says which service and which transport, each label starting with an underscore so it can never collide with a real host name: _sip._tcp.example.com is "the SIP service over TCP for example.com." The RDATA has four fields:

    • Priority: like MX preference. Clients must try every target at the lowest priority before touching the next one.
    • Weight: among targets with the same priority, how big a share of the clients each should get.
    • Port: the TCP or UDP port, so a service no longer has to live on its well-known port.
    • Target: the host name, which must have address records and must not be an alias. A target of "." means "this service is deliberately not offered here."

    The weight is the interesting part, because RFC 2782 spells out the exact algorithm every client should run. Within one priority level:

    1. Line the records up in any order, except that weight-0 records go first.
    2. Walk the list keeping a running sum of the weights.
    3. Pick a random whole number from 0 to the total, inclusive.
    4. Choose the first record whose running sum is at least that number. That target is tried next.
    5. Remove it and repeat with the rest, to build a full fall-back order.
    P(first pick = record i) ≈ weighti ÷ (total weight + 1)
    Worked example: weights 60, 30 and 10 sum to 100, and the random number has 101 possible values. The 60-weight record wins 60 or 61 of them, depending on whether it was first in the line-up; so shares come out at about 59.7%, 30.0% and 10.2% once the line-up is shuffled. The "+1" is the inclusive range, and it is also why a weight-0 record still gets picked now and then: it sits first and owns the number 0.
    ;; QUESTION SECTION:  (simulated)
    ;_sip._tcp.example.com.          IN  SRV
    ;; ANSWER SECTION:         prio weight port target
    _sip._tcp.example.com.  3600  IN  SRV  10 60 5060 sip1.example.com.
    _sip._tcp.example.com.  3600  IN  SRV  10 30 5060 sip2.example.com.
    _sip._tcp.example.com.  3600  IN  SRV  10 10 5060 sip3.example.com.
    _sip._tcp.example.com.  3600  IN  SRV  20  0 5060 standby.example.net.
    Go deeper: weight zero, and who actually uses SRV

    RFC 2782 says weight 0 means "very small chance of selection" and should be used when there is no reason to prefer one target over another. If every record at a priority has weight 0, the total is 0, the random number is always 0, and the first record in the line-up always wins, so a client that does not shuffle the line-up will send everyone to the same server. The SRV picker lets you see this: choose "All weights zero."

    SRV is used by SIP telephony, XMPP chat, LDAP and Kerberos in Microsoft's Active Directory, Minecraft servers, and a long list of internal service-discovery systems. Web browsers never adopted it, which is a large part of why HTTPS records exist.

    Instrument 2

    Run the SRV picker

    Each row is one SRV record for a simulated service. Press Run and the page plays ten thousand clients, each running the RFC 2782 algorithm from scratch with a shuffled line-up, then trying targets in that order until one is up. Bars show where clients really landed; the thick marks show the share that exact probability predicts. Tick "down" on a target to watch its clients move to its peers, and tick every target at one priority to watch them all fall to the next.

    Clients simulated–
    Priority used–
    Biggest gap vs expected–
    Clients with no server–

    A note to certificate authorities

    CAA: who may issue

    Any public certificate authority can issue a certificate for any domain, once it is convinced the requester controls the domain. A CAA record, now defined by RFC 8659, lets a domain owner narrow that list: "only these authorities may issue for me." Since 2017, the CA/Browser Forum's rules require public certificate authorities to check it before issuing.

    A CAA record has three parts: a flags byte, a tag, and a value. The tags you will use:

    • issue: an authority (named by its domain) allowed to issue certificates for this name. issue ";" means nobody may.
    • issuewild: the same, but only for wildcard certificates like *.example.com. When present, it overrides issue for wildcard requests.
    • iodef: where to report a refused request, as a mailto: or https: address.

    The clever part is the search. To issue for www.shop.example.com, the authority asks for CAA records at that exact name. If there are none, it removes the leftmost label and asks again at shop.example.com, then example.com, then com, stopping before the root. The first non-empty set it meets is the policy for the whole request; nothing above it is consulted. So one CAA set at your apex covers every name below it, and a subdomain can override it with its own set. If the climb finds nothing at all, any authority may issue.

    ; one policy at the apex covers the whole zone (simulated)
    example.com.       CAA  0 issue "ca.example.net"
    example.com.       CAA  0 issuewild ";"                    ; no wildcard certificates
    example.com.       CAA  0 iodef "mailto:security@example.com"
    ; a subdomain that uses a second authority overrides it completely
    shop.example.com.  CAA  0 issue "pki.example.org"

    The flags byte has one bit in use: 128, the critical flag. If an authority finds a critical tag it does not understand, it must refuse to issue. That lets a future tag say "if you can't enforce this, don't issue at all" instead of being quietly ignored.

    Go deeper: CNAMEs and the 2019 rewrite

    The original CAA standard, RFC 6844, climbed the tree not only along the name requested but also along every CNAME and DNAME target met on the way. That made the search slow and had an awkward side effect: a hosting company's CAA records could end up governing its customers' names. RFC 8659 replaced it with the simpler rule above. Each CAA query is an ordinary lookup, which still follows aliases, but the climb itself only walks up the requested name.

    CAA is a defense, not a guarantee. It only binds authorities that follow the rules, and an attacker who can change your DNS can change your CAA records too. Its value is in stopping mistakes and lazy validation at authorities you never chose.

    Telling the client how to connect

    SVCB and HTTPS records

    A browser visiting https://example.com traditionally learns almost nothing from DNS: an address or two. It then has to discover everything else by trying. Does the server speak HTTP/3 over QUIC? It finds out only after a first connection, from a header. Is there an encryption key for the TLS handshake? No way to say. RFC 9460, published in 2023, adds two record types to carry that information up front: SVCB, "service binding," for any protocol, and HTTPS, the same format specialised for the web.

    An HTTPS record starts with a priority. That number chooses between two modes.

    • AliasMode (priority 0) says "this service is really over there," naming a target. It is the CNAME the apex was never allowed: unlike a CNAME it only redirects this one service, so the apex keeps its SOA, NS and MX records untouched.
    • ServiceMode (priority 1 or more) describes an actual endpoint. Lower numbers are preferred, like MX. The target "." means "this same name." After the target come SvcParams, key=value pairs.
    SvcParamMeansExample
    alpnApplication protocols the endpoint speaksalpn=h2,h3 (HTTP/2 and HTTP/3)
    portA port other than the default 443port=8443
    ipv4hint, ipv6hintAddresses to try while the A/AAAA lookup is still in flightipv4hint=192.0.2.10
    echA key for Encrypted Client Hello, which hides the site name in the TLS handshake (Chapter 12 territory)ech=… (base64)
    mandatory, no-default-alpnKeys a client must understand; turn off the default protocolmandatory=alpn
    ; the apex hands web traffic to a CDN, without a CNAME (simulated)
    example.com.         HTTPS  0 cdn.example.net.
    ; the CDN's own name describes its endpoints
    cdn.example.net.     HTTPS  1 . alpn=h2,h3 ipv4hint=192.0.2.10 ipv6hint=2001:db8::10
    cdn.example.net.     HTTPS  2 alt.example.net. alpn=h2 port=8443

    A client that understands the record can open an HTTP/3 connection on its first try. A client that does not understand the record simply ignores it and falls back to A and AAAA, which is why sites keep both. For services other than the web, SVCB uses the same underscore naming as SRV: _8443._foo.api.example.com for a protocol "foo" on port 8443.

    Go deeper: mixing modes, and what parsers must do

    RFC 9460 is precise about mistakes. If an RRset contains an AliasMode record, clients must ignore any ServiceMode records in the same set, so mixing the two is pointless. SvcParams on an AliasMode record must be ignored, and zone-file parsers may warn about them. A client must ignore SvcParams it does not recognize, unless they are listed in mandatory, in which case it must skip that record. In presentation format, a key may not appear twice. The linter in Instrument 1 checks all of these.

    Answers for names that don't exist

    How a wildcard really matches

    A record whose owner starts with an asterisk label, like *.example.com, is a wildcard. It lets a server answer for names that are not in the zone. The intuitive rule, "the star matches anything," is wrong in two ways that matter, and RFC 4592 from 2006 exists to pin the real rule down.

    The real rule works in three steps. First, if the name asked about exists in the zone, the wildcard is never used, even if that name holds no record of the requested type; the answer is "no data." Second, if the name does not exist, the server finds the closest encloser: the longest ancestor of the name that does exist. Third, it looks for the source of synthesis, the name made of an asterisk plus the closest encloser. If that exists, the server builds an answer from it, with the owner name changed to the name you asked about. If it does not, the answer is NXDOMAIN.

    "Exists" is the subtle word. A name exists if it owns records, and also if any name below it owns records, even when it holds nothing itself. Such a name is an empty non-terminal. Put an SRV record at _ssh._tcp.host2.example and you have created _tcp.host2.example and host2.example as empty non-terminals. Now a question for host2.example hits an existing name: the wildcard at *.example stays silent, and the answer is "no data." The wildcard was blocked by a record you added for something else entirely.

    Two more consequences. A wildcard only matches at one place in the tree: *.example.com answers for a.b.example.com only if b.example.com does not exist. And an asterisk anywhere but the leftmost label, as in sub.*.example, is just an odd literal name, not a wildcard. Instrument 3 uses the example zone from RFC 4592 itself, so you can check each case against the standard.

    Go deeper: wildcards, delegation and DNSSEC

    Wildcards stop at zone cuts. If subdel.example is delegated to other servers, a question for host.subdel.example gets a referral, and the parent's wildcard has no say in it. A wildcard at *.example also cannot hold NS records sensibly; RFC 4592 discusses why delegating from a wildcard is a bad idea.

    Signed zones have to prove a wildcard answer is honest: that the name asked for really did not exist, and which wildcard was used. The NSEC and NSEC3 records that do this, and the "closest encloser proof," are in Chapter 11.

    Instrument 3

    Match a wildcard

    The zone below is the example from RFC 4592, section 2.2.1: one line per record, name then type. Pick an example query or type your own, and the matcher runs the RFC's steps: does the name exist, where is the delegation, which is the closest encloser, is there a source of synthesis. The tree draws the zone, with empty non-terminals dashed and the names that decided the answer marked. Edit the zone to see how one new record can switch a wildcard off.

    –

      Side by side

      Which record for which job

      You want to…UseWatch out for
      Point www at a hosting companyCNAMENothing else may live at www
      Point the bare domain at a CDNA/AAAA, provider flattening, or HTTPS AliasModeNever a CNAME at the apex
      Say where mail goesMXTarget must have addresses, not be a CNAME
      Publish a long verification or key stringTXTSplit into strings of 255 bytes or fewer
      Spread a non-web service over servers by shareSRVUnderscore names; clients must implement weights
      Limit which authorities issue certificatesCAAThe first set found climbing up wins outright
      Advertise HTTP/3, ports and hints to browsersHTTPSKeep A/AAAA for clients that ignore it
      Answer for any undefined subdomainWildcardAny existing name, even an empty one, blocks it
      Cheat sheet

      Terms from this chapter

      RRset
      All records with the same owner name, class and type. Answered, cached and expired as one unit, with one TTL.
      RDATA
      The data part of a record, whose format is set entirely by the record's type.
      Zone apex
      The top name of a zone, such as example.com itself. It always holds SOA and NS records, so it can never hold a CNAME.
      CNAME flattening
      A provider feature (also sold as ALIAS or ANAME) that resolves a target itself and answers with plain A/AAAA records. Not a standard record type.
      DNAME
      A record that aliases every name below its owner to the same names under another domain (RFC 6672).
      Character-string
      A length byte followed by up to 255 bytes. TXT RDATA is a list of them.
      SRV weight
      The share of clients each SRV target should get among targets of equal priority, chosen by RFC 2782's running-sum algorithm.
      CAA
      A record naming which certificate authorities may issue for a name. Authorities climb toward the root and obey the first set they find.
      SVCB / HTTPS
      Service-binding records (RFC 9460) that tell a client a service's target, protocols, port and address hints in one answer.
      AliasMode
      An SVCB/HTTPS record with priority 0: an alias for one service, legal at the apex.
      Closest encloser
      The longest existing ancestor of a name that does not exist. A wildcard can only answer from directly under it.
      Empty non-terminal
      A name with no records of its own that exists because names below it do. It blocks wildcard matching.
      Where this comes from

      Sources