(RFC) 1035 gives every DNS message the same 12-byte header (RFC 1035, section 4.1.1), which dig prints as the ->>HEADER<<- and flags: lines most readers skip. Those bytes say who answered, whether recursion was offered, whether the reply was cut short, whether a resolver vouches for it under the Domain Name System Security Extensions (), and how the request ended. In part 4 of DNS, read from the answer you decode a real exchange in hexadecimal and read each field for what it tells a diagnosis.

Twelve bytes, as RFC 1035 draws them

Six 16-bit words, drawn in 1987 (RFC 1035, section 4.1.1):

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Bit 0 is the most significant, and the high byte travels first (RFC 1035, section 2.3.2). The ID is "A 16 bit identifier assigned by the program that generates any kind of query." and the reply copies it (section 4.1.1). The flags are (query or response), AA (Authoritative Answer), TC (Truncated Response), RD (Recursion Desired) and RA (Recursion Available), beside a 4-bit opcode and a 4-bit (response code); dig labels the four section counts QUERY, ANSWER, AUTHORITY and ADDITIONAL (dig.c).

Z, bits 9 to 11, was "Reserved for future use. Must be zero in all queries and responses" (RFC 1035, section 4.1.1) until RFC 2535 took two of its bits for DNSSEC in 1999, (Authentic Data) and CD (Checking Disabled) (RFC 2535, section 6.1). The word now reads (RFC 6895, section 2):

             |QR|   OpCode  |AA|TC|RD|RA| Z|AD|CD|   RCODE   |

One reserved bit remains, assignable only by a Standards Action (RFC 6895, section 2.1). In the numbering of the Internet Assigned Numbers Authority () registry (IANA, DNS parameters), bit n is worth 2 to the power 15 minus n:

bit      0     1-4     5     6     7     8     9     10    11    12-15
field    QR    Opcode  AA    TC    RD    RA    Z     AD    CD    RCODE
mask     8000  7800    0400  0200  0100  0080  0040  0020  0010  000F

A real query and its answer, byte by byte

Captured on 18 October 2026: example.com's name server (NS) records, asked of a public resolver over (), which carries the message in wire format, header first (RFC 8484, section 4.1.1). The query:

0000  11 49 01 20 00 01 00 00  00 00 00 01 07 65 78 61
0010  6d 70 6c 65 03 63 6f 6d  00 00 02 00 01 00 00 29
0020  04 d0 00 00 00 00 00 00

11 49 is the ID, 0x1149 = 4,425, and 01 20 the flags; the counts are 1, 0, 0 and 1. From byte 12 come example.com, type 2 (NS) and class 1 (IN); the last 11 bytes are an Extension Mechanisms for DNS () OPT pseudo-record, type 41, offering a 1,232-byte UDP payload (RFC 6891, section 6.1.2). The answer begins:

0000  11 49 81 a0 00 01 00 02  00 00 00 01 07 65 78 61
0010  6d 70 6c 65 03 63 6f 6d  00 00 02 00 01

Same ID and question; the counts are 1, 2, 0 and 1. Bytes 29 to 83 are the two NS records, the first opening with c0 0c, a pointer to offset 12 counted from the ID (RFC 1035, section 4.1.4); bytes 84 to 94 are the resolver's OPT, because "If an record is present in a received request, compliant responders MUST include an OPT record in their respective responses" (RFC 6891, section 6.1.1). The flags, against the masks:

         QR    Opcode  AA    TC    RD    RA    Z     AD    CD    RCODE
query    0     0000    0     0     1     0     0     1     0     0000    0x0120
answer   1     0000    0     0     1     1     0     1     0     0000    0x81a0

RD (256) and AD (32) make the query's 288, 0x0120; the answer adds QR (32,768) and RA (128): 32,768 + 256 + 128 + 32 = 33,184, 0x81a0. RD returns as sent, AA is clear because a resolver answered, RCODE 0 is NOERROR (No Error), and AD is set because example.com is signed and the query asked (RFC 6840, section 5.8). Illustrative, from dig 9.20.29's print formats, the pair as +qr would show it (dig.c):

;; Sending:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4425
;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4425
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

ADDITIONAL: 1 with no additional section printed is normal: the count includes the OPT record, which dig prints apart under ;; OPT PSEUDOSECTION: (message.c), and an unparseable reply appears only as ;; Got bad packet: (dighost.c). EDNS and the OPT Pseudo-Section reads that block.

What the flags tell a diagnosis

QR and AA. Output without qr is a query, since QR is 1 only in responses (RFC 1035, section 4.1.1). AA "specifies that the responding name server is an authority for the domain name in question section" (section 4.1.1), though behind an alias only the alias record is sure to be authoritative (RFC 2181, section 5.4.1). Resolvers' answers, valid NXDOMAIN (Non-Existent Domain) replies included, normally lack aa; an authoritative reply to a query with RD set would be 0x8500, QR AA RD (illustrative). See Authoritative vs Non-Authoritative Answers.

RD and RA. "If RD is set, it directs the name server to pursue the query recursively." and the response copies it (RFC 1035, section 4.1.1); dig sets it unless told +norecurse (dig manual). RA is the offer: "That is, RA signals availability rather than use" (RFC 1034, section 4.3.1). For rd without ra dig warns recursion requested but not available (dig.c); an authoritative server still answers its own zones and generally returns REFUSED for others (RFC 9520, section 2.2).

AD and CD. A validating resolver sets AD only when it holds the answer and authority records authentic (RFC 4035, section 3.2.3) and the query carried AD or the DNSSEC OK (DO) bit (RFC 6840, section 5.8), as dig's do by default (dig manual). It is a claim, not a proof: a resolver "MUST disregard the meaning of the CD and AD bits in a response unless the response was obtained by using a secure channel" (RFC 4035, section 4.6). Trust ad only from a validating resolver you trust, over a channel you trust (RFC 4033, section 7).

CD switches validation off for one query and comes back copied (RFC 4035, section 3.2.2); a failed validation returns SERVFAIL (Server Failure) unless the query set CD (section 5.5). Hence the test from the Internet Systems Consortium (ISC), dig's publisher: "If the query succeeds with dig +cd, but ends in SERVFAIL without it, you know you are dealing with a validation problem" (ISC, DNSSEC Guide). On 18 October 2026 a public resolver answered the guide's domain built to fail like this (192.0.2.53 stands for your resolver):

dig @192.0.2.53 www.dnssec-failed.org A
dig @192.0.2.53 www.dnssec-failed.org A +cd
without CD  04 6e 81 82 00 01 00 00 00 00 00 01   0x8182: qr rd ra, SERVFAIL, no answer
with CD     84 33 81 90 00 01 00 02 00 00 00 01   0x8190: qr rd ra cd, NOERROR, 2 answers

DNSSEC end to end explains the validation.

TC: when the answer does not fit

RFC 1035 limited UDP messages to 512 bytes, IP and UDP headers aside, and set TC on longer ones it truncated (RFC 1035, section 4.2.1). RFC 2181 narrowed the rule to a missing resource record set (RRSet): "The TC bit should be set in responses only when an RRSet is required as a part of the response, but could not be included in its entirety." A client getting TC should ignore the reply and ask again over a transport that allows larger ones, such as TCP (RFC 2181, section 9).

DNSSEC made truncation at 512 bytes commonplace (RFC 7766, section 4), and RFC 7766 ruled: "All general-purpose DNS implementations MUST support both UDP and TCP transport" (section 5). Networks too: "Likewise, network operators MUST allow DNS service over both UDP and TCP transports" (RFC 9210, section 3). A TCP block fails quietly, small answers working over UDP and large ones failing (RFC 7766, section 1).

dig retries over TCP by default (dig manual), noting Truncated, retrying in TCP mode. (dighost.c); +ignore keeps the truncated UDP answer and +tcp tests the server's TCP port 53 directly (RFC 1035, section 4.2.2):

dig +ignore @192.0.2.53 example.com
dig +tcp @192.0.2.53 example.com

A truncated resolver answer would read flags: qr tc rd ra, 32,768 + 512 + 256 + 128 = 33,664 = 0x8380 (illustrative). The dig command builder writes both commands; Anatomy of a dig command line explains them.

Opcode and status: the codes you will meet

The opcode is set by the originator and copied into the response (RFC 1035, section 4.1.1), each unit adding 2,048 to the flags word (IANA, DNS parameters):

OpcodeNameFlags wordNote
0QUERY0x0000every lookup
1IQUERY (inverse query)0x0800obsolete (RFC 3425, section 3)
2STATUS0x1000never specified (RFC 6895, section 2.2)
4NOTIFY0x2000zone change notice; the request sets AA, 0x2400 (RFC 1996, abstract and section 4.5)
5UPDATE0x2800dynamic update; QR its only flag (RFC 2136, section 2.2)
6DSO (DNS Stateful Operations)0x3000over TCP or TLS; dig prints RESERVED6 (RFC 8490, section 4.2; dig.c)

The status is the RCODE (RFC 1035, section 4.1.1); the codes you will meet, with the registry's descriptions (IANA, DNS parameters):

CodeStatusRegistry descriptionUsually means
0NOERRORNo Errorprocessed; an empty answer is
1FORMERRFormat Errornot understood, often EDNS (RFC 9520, section 2.7)
2SERVFAILServer Failureno valid data, or failed validation (RFC 9520, sections 2.1 and 2.6)
3NXDOMAINNon-Existent Domainthe name does not exist
4NOTIMPNot Implementedkind of query or opcode unsupported (RFC 2136, section 2.2)
5REFUSEDQuery Refusedpolicy, or a zone not served (RFC 9520, section 2.2)
16BADVERSBad OPT VersionEDNS version not implemented

NODATA has no code of its own: "NODATA responses have to be algorithmically determined from the response's contents as there is no RCODE value to indicate NODATA" (RFC 2308, section 2.2). Resolvers MUST cache a SERVFAIL for 1 second to 5 minutes, so a repaired fault can linger (RFC 9520, section 3.2).

EDNS widens the RCODE to 12 bits: the first byte of the OPT record's extended RCODE and flags field supplies the upper eight (RFC 6891, section 6.1.3); dig prints the sum, that byte times 16 plus the header's four bits (message.c). Two captures of the same day:

dig +edns=1 +showbadvers example.com NS
example.com NS, EDNS version 1  15 b6 81 80 00 01 00 00 00 00 00 01  qr rd ra; OPT: extended RCODE 1
no-such-name.invalid A          e7 26 81 a3 00 01 00 00 00 01 00 01  qr rd ra ad; RCODE 3

The first header says RCODE 0, but its OPT adds extended RCODE 1: 1 × 16 + 0 = 16, BADVERS, which a responder MUST return for an EDNS version it does not implement (RFC 6891, section 6.1.3); dig retries with version 0 unless told +showbadvers (dig manual). The second is an NXDOMAIN relayed by a resolver, AA clear, with the root zone's SOA as its authority record, whose lifetime caps negative caching (RFC 2308, section 3). The name is under .invalid because example.com now answers NOERROR with no data for names that do not exist, through compact denial of existence (RFC 9824, section 1), while RFC 6761 lets users assume that .invalid names always return NXDOMAIN (RFC 6761, section 6.4).

The ID, the source port and forged answers

RFC 5452 makes the full match, addresses, port, ID, name, class and type, a MUST: "A mismatch and the response MUST be considered invalid" (RFC 5452, section 9.1). dig randomises the ID per lookup and warns ID mismatch on a stray reply (dighost.c).

A caching resolver accepts the first response meeting four conditions, among them a matching ID and arrival on the address and port the question left from, then serves any forgery to its clients (RFC 5452, section 3). A random 16-bit ID alone "will require on average 32768 attempts to guess" (section 4.3); a random source port adds up to 16 bits (section 4.5), and at full width the average becomes 32,768 × 65,536 = 2,147,483,648. RFC 5452 therefore requires an unpredictable port from the widest practicable range, and "Use an unpredictable query ID for outgoing queries, utilizing the full range available (0-65535)" (section 9.2). VU#800113 (July 2008) credited with showing how practical cache poisoning was, and with the original idea of randomised source ports (CERT Coordination Center, VU#800113), as The Kaminsky flaw tells.

Limits remain: devices that serialise or cap source ports can dramatically reduce the gain (RFC 5452, section 10), and "protocol users are not protected against third parties with the ability to observe, modify, or inject packets in the traffic of a resolver" (section 1), a case left to DNSSEC.

Seeing the header yourself

In dig, +qr shows the query as sent, and +qid=value, +opcode=value and +zflag set the ID, the opcode and Z for tests (dig manual); dig Query Options and Output Control has the rest. Scripts must read the status, as dig exits 0 for NXDOMAIN too (dig manual, Return Codes). tcpdump marks RD set with +, AA set with *, RA clear with - and TC set with | (tcpdump(1)); Wireshark's fields are listed below (Wireshark, DNS fields); and Python's struct.unpack('!HHHHHH', data[:12]) returns the six words in network byte order (Python, struct).

tcpdump -n -X 'port 53'
dns.id  dns.flags  dns.flags.response  dns.flags.opcode  dns.flags.authoritative
dns.flags.truncated  dns.flags.recdesired  dns.flags.recavail  dns.flags.z
dns.flags.authenticated  dns.flags.checkdisable  dns.flags.rcode  dns.resp.ext_rcode
dns.count.queries  dns.count.answers  dns.count.auth_rr  dns.count.add_rr  dns.response_to

The dig output explainer reads pasted header and flags lines, explains the seven flags and warns on tc, a missing qr and do without ad. The Python playground packs a DNS header in struct; the nslookup output explainer says whether an answer is authoritative.

What to remember

  • The header is 12 bytes, six 16-bit words: the ID, the flags word and four section counts.
  • The flags word is a sum of masks: QR 0x8000, RD 0x0100, RA 0x0080 and AD 0x0020 make 0x81a0, a resolver's validated NOERROR.
  • RD is copied back, RA is the server's offer, and AA marks the zone's own server, so resolvers' answers lack it.
  • TC means a needed record set did not fit: retry over TCP, which implementations MUST support and networks MUST allow.
  • AD is a claim to trust only from a validating resolver you trust; SERVFAIL without CD and an answer with CD mean a DNSSEC failure.
  • The status is a 12-bit RCODE, four header bits plus eight in the OPT record, which ADDITIONAL counts though dig prints it apart.
  • The ID and the source port must both be unpredictable: 16 bits alone fall to 32,768 guesses on average.

Sources