The Domain Name System (DNS) is the Internet's distributed lookup protocol: it turns a name such as www.example.com into the data a client needs to connect, most often an IP address, by asking a chain of servers that each know one part of the namespace. It is an application layer protocol defined by RFC 1034 and RFC 1035 (STD 13, November 1987), and it runs over UDP and TCP port 53, with encrypted variants on port 853 and over HTTPS.
Every DNS exchange uses the same small binary message: a 12 byte header with a 16 bit ID and a set of flags, then a question and three sections of resource records. Most DNS security problems trace back to that format and to the trust a resolver places in whatever arrives on port 53. This post covers the format field by field, the resolution path, the record types that matter, the extensions that changed the protocol and how to harden and check a resolver you run.
We already have narrower posts on recursive versus authoritative servers, DNSSEC, DoH versus DoT versus DNSSEC, DNS attack techniques, zone transfers and CAA records. This is the protocol reference underneath them.
Key takeaways
- RFC 1034 (concepts) and RFC 1035 (implementation) are still the core of DNS. Together they are Internet Standard 13 and obsolete RFC 882, RFC 883 and RFC 973. Dozens of later RFCs update them, and RFC 9499 (BCP 219, March 2024) is the current terminology reference.
- A DNS message has a 12 byte header (ID, flags, opcode, response code and four section counts) and four sections: question, answer, authority and additional. The same format carries queries and responses.
- Classic DNS uses UDP and TCP port 53. RFC 1035 caps plain UDP messages at 512 bytes; EDNS(0) (RFC 6891, STD 75, April 2013) lets a client advertise a larger buffer, and current resolver defaults sit at 1232 bytes to avoid IP fragmentation.
- RFC 7766 (March 2016) makes TCP support mandatory for every general purpose DNS implementation, and RFC 9210 (BCP 235, March 2022) makes allowing DNS over TCP an operational Best Current Practice.
- Plain DNS has no confidentiality, no integrity protection and no origin authentication. A resolver accepts an answer because the ID, ports, addresses and question match, which is why RFC 5452 requires random source ports and random IDs.
- DNSSEC adds origin authentication and integrity; DNS over TLS (RFC 7858), DNS over HTTPS (RFC 8484) and DNS over QUIC (RFC 9250, May 2022) add confidentiality on one hop. None of them alone does both.
- A safe resolver answers only its own clients (RFC 5358, BCP 140), validates DNSSEC, minimises QNAMEs (RFC 9156), keeps UDP answers under the fragmentation limit, accepts TCP and rate limits responses. BIND 9 and Unbound ship most of this on by default.
What DNS is for, the layer and the standard
The problem DNS solves
Before DNS, every host on the ARPANET relied on a single HOSTS.TXT file maintained centrally. RFC 1034 replaced it with a tree of labels (each up to 63 octets, a full name up to 255) with resource records attached to the nodes, name servers that hold authoritative data for parts of the tree called zones and delegate the rest, and resolvers that follow those delegations for clients and cache what they learn.
Where it sits
DNS is an application layer protocol. It runs over UDP and TCP on port 53 (RFC 1035, section 4.2), over TLS on TCP port 853 (RFC 7858), over QUIC on UDP port 853 (RFC 9250) and inside HTTPS (RFC 8484). Our TCP versus UDP post covers what each transport exposes, and the UDP explainer covers the datagram side.
The defining standards
| Document | Date | Status | What it does |
|---|---|---|---|
| RFC 1034, Domain Names: Concepts and Facilities | November 1987 | STD 13 | Name space, zones, delegation, resolver and server behaviour |
| RFC 1035, Domain Names: Implementation and Specification | November 1987 | STD 13 | Message format, RR formats, master file format, transport |
| RFC 2181, Clarifications to the DNS Specification | July 1997 | Standards Track | RRsets, TTL rules, data ranking and trust; updates 1034, 1035 and 1123 |
| RFC 6891, Extension Mechanisms for DNS (EDNS(0)) | April 2013 | STD 75 | OPT pseudo record, larger UDP payloads, extended flags; obsoletes 2671 and 2673 |
| RFC 7766, DNS Transport over TCP | March 2016 | Standards Track | TCP mandatory; pipelining, out of order replies; obsoletes 5966, updates 1035 and 1123 |
| RFC 9499, DNS Terminology | March 2024 | BCP 219 | Current definitions; obsoletes RFC 8499 |
RFC 1034 and RFC 1035 obsolete RFC 882, RFC 883 and RFC 973 and have never been replaced. Instead, each later RFC carries an "Updates: 1034, 1035" line for the part it changes.
What RFC 1034 and RFC 1035 still define today
Most of the 1987 text is still normative and still on the wire: the 12 byte header and four sections, the resource record format, length prefixed labels with compression pointers, the core types (A, NS, CNAME, SOA, PTR, MX and TXT), zones and delegation with the SOA timers that drive secondaries, the master file syntax that BIND, Knot and NSD still read, and port 53. Extensions reuse spare space (two reserved Z bits became AD and CD for DNSSEC) or add records (OPT for EDNS) rather than change the layout.
What changed is detail. The 512 byte UDP limit became a floor thanks to EDNS(0). TCP went from fallback to mandatory. The IQUERY opcode is marked obsolete in the IANA registry. TTL rules were clarified by RFC 2181 and extended by serve stale (RFC 8767). And the 1987 assumption that people on the network were colleagues has been replaced by the hardening RFCs below.
How DNS works on the wire
The message: one format, five parts
RFC 1035 section 4.1 defines a single message format used for queries and responses in both directions:
+====================-+
| Header | 12 bytes, always present
+====================-+
| Question | the question for the name server
+====================-+
| Answer | RRs answering the question
+====================-+
| Authority | RRs pointing toward an authority
+====================-+
| Additional | RRs holding additional information
+====================-+
A query carries one question (plus an OPT record if the client speaks EDNS). A response copies the question back and fills in the record sections that apply; a referral from a root or TLD server has an empty answer, NS records in the authority section and glue addresses in the additional section.
The header, field by field
The header is six 16 bit words. This is the layout from RFC 1035 section 4.1.1, with the two flag bits that DNSSEC later claimed from the reserved Z field:
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|AD|CD| RCODE |
+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+
| QDCOUNT |
+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+
| ANCOUNT |
+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+
| NSCOUNT |
+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+
| ARCOUNT |
+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+
| Field | Size | Meaning | Security note |
|---|---|---|---|
| ID | 16 bits | Chosen by the querier, copied into the reply, used to match replies to queries | Only 65,536 values. On its own it is far too small to stop forged replies, which is why source port randomisation matters |
| QR | 1 bit | 0 for a query, 1 for a response | |
| Opcode | 4 bits | Kind of query: 0 Query, 2 Status, 4 Notify, 5 Update, 6 DNS Stateful Operations; 1 (IQuery) is obsolete | Notify and Update should be restricted to known peers |
| AA | 1 bit | Authoritative Answer: the responding server is an authority for the name in the question | |
| TC | 1 bit | Truncated: the message did not fit the transport, so retry over TCP | A client that ignores TC and never retries over TCP will fail on large answers |
| RD | 1 bit | Recursion Desired, set by the client and copied into the reply | |
| RA | 1 bit | Recursion Available, set by the server | An Internet facing server that sets RA for anyone is an open resolver |
| Z | 1 bit | Reserved, must be zero | |
| AD | 1 bit | Authentic Data: the resolver validated the answer with DNSSEC (RFC 4035, RFC 6840) | Only meaningful if the path from your resolver to you is trusted |
| CD | 1 bit | Checking Disabled: the client asks the resolver not to validate | |
| RCODE | 4 bits | Response code; EDNS extends it to 12 bits | |
| QDCOUNT | 16 bits | Number of entries in the question section | |
| ANCOUNT | 16 bits | Number of RRs in the answer section | |
| NSCOUNT | 16 bits | Number of RRs in the authority section | |
| ARCOUNT | 16 bits | Number of RRs in the additional section |
The response codes you will meet most often are in the IANA DNS RCODEs registry:
| RCODE | Name | What it means in practice |
|---|---|---|
| 0 | NoError | The query succeeded. An answer section with zero records is NODATA: the name exists but not with that type |
| 1 | FormErr | The server could not parse the query |
| 2 | ServFail | The server failed to answer, for example because DNSSEC validation failed or no authority responded |
| 3 | NXDomain | The name does not exist |
| 5 | Refused | Policy refusal, for example recursion requested by a client outside the allowed list |
| 23 | BADCOOKIE | Missing or bad server cookie (RFC 7873) |
The question section and the resource record format
The question section has three fields: QNAME (the name being asked about), QTYPE (the record type, such as A or AAAA) and QCLASS (almost always IN, for Internet). Every resource record in the answer, authority and additional sections has the same six field layout from RFC 1035 section 4.1.3:
| Field | Size | Meaning |
|---|---|---|
| NAME | variable | The owner name, encoded as labels or as a compression pointer |
| TYPE | 16 bits | The record type code from the IANA registry |
| CLASS | 16 bits | Usually IN (1) |
| TTL | 32 bits | How many seconds the record may be cached |
| RDLENGTH | 16 bits | Length of RDATA in bytes |
| RDATA | variable | The record data, whose format depends on TYPE |
Names on the wire are a sequence of labels, each a one byte length followed by that many bytes, ending with a zero length label for the root. The top two bits of the length byte are always zero for a normal label, which is where the 63 octet limit comes from. When both top bits are set, the byte starts a two byte compression pointer: the remaining 14 bits give the offset of an earlier name in the same message. Compression saves space, and it has historically been a rich source of parser bugs, because a pointer is just an offset that the parser must bounds check and guard against loops. That applies to the resolver library in your application runtime as much as to DNS servers.
Ports and transports
| Transport | Port | Defined in | Notes |
|---|---|---|---|
| UDP | 53 | RFC 1035 | Default for queries; 512 byte limit without EDNS |
| TCP | 53 | RFC 1035, RFC 7766 | Each message prefixed with a two byte length field; mandatory to support |
| DNS over TLS (DoT) | TCP 853 | RFC 7858 | Same length prefixed framing inside TLS |
| DNS over QUIC (DoQ) | UDP 853 | RFC 9250 | One query per QUIC stream |
| DNS over HTTPS (DoH) | HTTPS | RFC 8484 | DNS wire format in an HTTP request body or GET parameter |
RFC 1035 says UDP is "not acceptable for zone transfers" and recommends a minimum retransmission interval of 2 to 5 seconds, trying other servers first.
How a query travels from stub to root, TLD and authoritative servers
RFC 9499 gives the roles their current names. A stub resolver "cannot perform all resolution itself" and depends on a recursive resolver, which acts in recursive mode and is normally expected to cache what it learns. Authoritative servers hold the zones. A forwarder sits in between and sends queries on to another resolver rather than walking the tree itself. Our post on recursive versus authoritative servers covers the roles in depth; here is what happens on the wire for a cold cache lookup of www.example.com, type A.
Stub (laptop) Recursive resolver Root server .com TLD server example.com authoritative
| | | | |
|== Q www.example.com A, RD=1 ==> | | |
| |== Q com NS (minimised) -> | |
| |<- referral: com NS + glue, AA=0 == | |
| |== Q example.com NS (minimised) ============> |
| |<- referral: example.com NS + glue, AA=0 ==- |
| |== Q www.example.com A (full name now) ========================->|
| |<- answer: www.example.com A 192.0.2.10, AA=1 ==================-|
| | (validate DNSSEC if signed, cache for TTL) |
|<- answer, RA=1, AD=1 if validated == |
Three points matter for security. The stub trusts its resolver completely, and that hop, often across Wi-Fi or an ISP network, is where encrypted DNS earns its keep. The resolver trusts referrals, so RFC 2181 and RFC 5452 tell it to accept only records within the bailiwick of the server that sent them. And every hop is a separate UDP exchange with its own ID and port, each a chance for a forged reply to arrive first; whatever gets into cache is served to every client for its whole TTL.
The diagram shows QNAME minimisation, covered below; without it the root would see the full question.
Caching, TTLs and negative answers
- Negative caching. RFC 2308 specifies negative caching, so answers saying a name or type does not exist are cached too. RFC 8020 (November 2016) says that NXDOMAIN means there is really nothing underneath a name, so a resolver can stop asking about names below it.
- Caching failures. RFC 9520 (December 2023) makes resolvers cache resolution failures for "at least 1 second" and never longer than 5 minutes, so a broken zone does not trigger a storm of retries.
- Serve stale. RFC 8767 (March 2020) lets a resolver serve expired data when the authoritative servers cannot be reached, with a suggested cap of 7 days. BIND 9 (stale-answer-enable) and Unbound (serve-expired) both ship it switched off.
- Aggressive use of the DNSSEC chain. RFC 8198 (July 2017) lets a validating resolver use cached NSEC records to synthesise NXDOMAIN answers without asking upstream. Unbound's aggressive-nsec option defaults to yes.
The main record types and what they do
All type codes below come from the IANA Resource Record (RR) TYPEs registry.
| Type | Code | Defined in | What it holds | Security relevance |
|---|---|---|---|---|
| A | 1 | RFC 1035 | An IPv4 address | The most forged record in history |
| NS | 2 | RFC 1035 | An authoritative name server for the zone | Delegation; a hijacked NS hands over the whole zone |
| CNAME | 5 | RFC 1035 | The canonical name for an alias | Dangling CNAMEs to deprovisioned cloud resources enable subdomain takeover |
| SOA | 6 | RFC 1035 | Start of authority: primary server, contact, serial, timers, negative TTL | Serial and timers drive secondaries and negative caching |
| PTR | 12 | RFC 1035 | A name for an address in reverse zones | Not proof of identity; anyone controlling the reverse zone sets it |
| MX | 15 | RFC 1035 | Mail exchanger with preference | Mail routing; protected by DNSSEC and MTA-STS, not by DNS itself |
| TXT | 16 | RFC 1035 | Text strings | Used for SPF, DKIM keys, DMARC policy and domain ownership proofs |
| AAAA | 28 | RFC 3596 | An IPv6 address | Same risks as A; often forgotten in filtering |
| OPT | 41 | RFC 6891 | EDNS pseudo record, never stored in zones | Carries buffer size, DO bit, cookies, padding |
| DS | 43 | RFC 4034 | Hash of a child zone's key, held in the parent | DNSSEC chain of trust |
| RRSIG | 46 | RFC 4034 | A DNSSEC signature over an RRset | |
| DNSKEY | 48 | RFC 4034 | A zone's DNSSEC public key | |
| SVCB | 64 | RFC 9460 | Service binding: endpoint and parameters for any service | |
| HTTPS | 65 | RFC 9460 | SVCB for HTTPS origins | Carries ALPN, ports, address hints and encrypted ClientHello configuration |
| CAA | 257 | RFC 8659 | Which certificate authorities may issue for the domain | Limits mis-issuance by a CA that honours it |
Two meta types appear only in queries and transfers: AXFR (252) for full zone transfers and IXFR (251) for incremental ones. Zone transfers are covered in our AXFR post; the short version is that only your own secondaries should be allowed to request them. The ANY query type (registered as "*", code 255) asks for all records at a name, and RFC 8482 (January 2019) lets servers answer it with a minimal subset, because large ANY answers were a favourite tool for amplification.
SVCB and HTTPS records
RFC 9460 (November 2023) added the most important new record types of the last decade. SVCB has two modes: AliasMode, which "simply delegates operational control for a resource", and ServiceMode, which "binds together configuration information for a service endpoint". ServiceMode records carry key and value parameters such as alpn (which application protocols the server speaks, for example HTTP/3), port, ipv4hint and ipv6hint. The HTTPS record is the SVCB form for web origins.
An HTTPS record lets a browser learn in one lookup that a site speaks HTTP/3 and where to connect, and RFC 9460 lists conveying Encrypted ClientHello keys among its uses. A forged HTTPS record can strip those hints, so DNS integrity matters more, with TLS certificate checks as the backstop.
CAA
CAA records tell certificate authorities which of them may issue for a domain, with issue, issuewild and iodef properties and a critical flag (RFC 8659, November 2019, which obsoletes RFC 6844). Our CAA post covers the syntax and rollout.
How EDNS(0), DNS over TCP and QNAME minimisation change the picture
EDNS(0): bigger messages and room for options
The 512 byte UDP limit from RFC 1035 could not hold DNSSEC signatures, IPv6 glue or long TXT records. EDNS(0), now RFC 6891 (STD 75, April 2013), solves this without changing the header. A client that supports it adds a single OPT pseudo record to the additional section:
| OPT field | Normal RR meaning | EDNS meaning |
|---|---|---|
| NAME | Owner name | Must be 0 (the root) |
| TYPE | Record type | OPT (41) |
| CLASS | Class | The requester's UDP payload size |
| TTL | Time to live | Extended RCODE (8 bits), EDNS version (8 bits), DO flag and other flags |
| RDLEN | Data length | Length of all options |
| RDATA | Record data | A list of option code, length and value entries |
The IANA EDNS flags registry defines DO (DNSSEC answer OK) as bit 0, which tells the server the client wants signatures. Common options in the EDNS option codes registry are edns-client-subnet (8, RFC 7871), COOKIE (10, RFC 7873), Padding (12, RFC 7830) and Extended DNS Error (15, RFC 8914).
On payload size, RFC 6891 treats values below 512 as 512 and suggests 4096 as a starting point, while warning that very large values guarantee fragmentation. RFC 9715 (January 2025) moved practice the other way: it describes the 1232 byte value from DNS Flag Day 2020 (the IPv6 minimum MTU of 1280 minus 48 bytes of headers) and recommends a 1400 byte maximum, with larger answers moving to TCP. BIND 9.20 (edns-udp-size, max-udp-size) and Unbound 1.26 (edns-buffer-size) both default to 1232.
DNS over TCP: from fallback to requirement
Many operators once treated TCP port 53 as "zone transfers only" and blocked it. RFC 7766 (March 2016) ended that: "All general-purpose DNS implementations MUST support both UDP and TCP transport", for authoritative servers, recursive servers and stub resolvers alike. It also describes connection reuse, pipelining and out of order responses, which make TCP far cheaper than one query per connection.
RFC 9210 (BCP 235, March 2022) makes permitting DNS over TCP on the Internet a Best Current Practice. In practice: allow TCP port 53 wherever UDP port 53 is allowed, retry over TCP when TC is set, and manage connections; RFC 7766's security considerations suggest limits on total connections, connections per source, idle time, transactions per connection and connection duration. TCP's handshake also means a forged source cannot complete a connection, so TCP answers are not a reflection vector and are much harder to forge off path.
QNAME minimisation: tell each server only what it needs
Traditionally a resolver sent the full name and type to every server on the path. RFC 9156 (November 2021, obsoleting the experimental RFC 7816) describes QNAME minimisation: send each server the name "stripped to just one label more than the longest matching domain name for which the name server is known to be authoritative", with a QTYPE chosen to obscure the original. RFC 9156 relaxes the earlier advice to use NS as that type and notes that A or AAAA "will also blend into traffic". It recommends capping the extra iterations (MAX_MINIMISE_COUNT) at 10.
Unbound's qname-minimisation defaults to yes and uses QTYPE A where possible; BIND 9's qname-minimization defaults to relaxed. Both fall back to the full name when an upstream misbehaves; strict modes never fall back and break some zones.
DNS cookies
DNS Cookies (RFC 7873, May 2016, updated by RFC 9018 in April 2021) add a lightweight proof of return routability to UDP DNS. The client sends an 8 byte client cookie in an EDNS option; the server returns a server cookie of 8 to 32 bytes; later queries carry both. A forged response without the right client cookie can be dropped. BIND answers cookies by default (answer-cookie yes), and Unbound's ip-ratelimit lets "clients with a valid DNS Cookie" bypass the per address limit. Cookies are not cryptographic authentication of data; they raise the bar for blind spoofing and reflection.
Versions and history
DNS has no version number in its header. It evolves through extensions negotiated in band (EDNS) and through updates to how implementations behave.
| Date | Change | Why it mattered |
|---|---|---|
| November 1987 | RFC 1034 and RFC 1035 (STD 13), obsoleting RFC 882, 883 and 973 | The protocol as still deployed |
| March 2005 | RFC 4033 to 4035, DNSSEC as deployed today | Origin authentication and integrity for DNS data |
| 8 July 2008 | CVE-2008-1447 published: resolvers with insufficient randomness could be poisoned using in bailiwick referrals | Drove source port randomisation everywhere |
| October 2008 | RFC 5358 (BCP 140), closing open recursive resolvers | Reflection and amplification |
| January 2009 | RFC 5452, resilience against forged answers | Random ports and IDs become MUST |
| April 2013 | RFC 6891 (STD 75), EDNS(0) | Larger messages, flags, options |
| March 2016 | RFC 7766, TCP mandatory | Large answers and connection reuse |
| May 2016 | RFC 7858 DNS over TLS; RFC 7873 DNS Cookies | Encrypted stub to resolver hop; anti spoofing |
| October 2018 | RFC 8484 DNS over HTTPS | DNS inside HTTPS |
| November 2021 | RFC 9156 QNAME minimisation (Standards Track) | Privacy toward upstream servers |
| March 2022 | RFC 9210 (BCP 235), DNS over TCP as an operational requirement | Ends "block TCP 53" |
| May 2022 | RFC 9250 DNS over QUIC | Encryption without TCP head of line blocking |
| November 2023 | RFC 9460 SVCB and HTTPS records | Service bindings and Encrypted ClientHello delivery |
| January 2025 | RFC 9715 on avoiding IP fragmentation | 1400 byte recommended UDP maximum |
Deprecated in practice: IQUERY, full ANY answers over UDP, 4096 byte UDP buffers and blocking TCP port 53. Still everywhere: plain UDP port 53, both inside networks and between resolvers and authoritative servers. RFC 9250 defines encryption for the resolver to authoritative leg, but the stub to resolver leg is where encryption is most deployed.
Security properties: what DNS does and does not guarantee
Start from what the protocol gives you by itself, then add each extension.
| Property | Plain DNS (UDP or TCP 53) | With DNSSEC | With DoT, DoH or DoQ |
|---|---|---|---|
| Confidentiality of queries | None. Every query and answer is readable on the path | None. RFC 4033 says DNSSEC does not provide confidentiality | Yes, on the encrypted hop only. The resolver still sees everything |
| Integrity of answers | None beyond UDP and TCP checksums, which an attacker can recompute | Yes, for signed zones, end to end from the zone to the validating resolver | Yes, on the encrypted hop only |
| Origin authentication of data | None. A reply is accepted if it matches ID, ports, addresses and question | Yes, via the chain from the root key | No. It authenticates the resolver, not the data |
| Authenticated denial of existence | None | Yes, via NSEC or NSEC3 | No |
| Replay protection | Matching rules only | Signatures have validity windows | Provided by TLS or QUIC on the hop |
| Availability | Exposed to floods, reflection and resource exhaustion | Larger answers add load and amplification potential | TCP and QUIC handshakes resist spoofing; servers carry more connection state |
The key point is that plain DNS authenticates nothing. RFC 5452 section 9.1 lists what a resolver must match before accepting a response: source address, destination address, destination port, query ID, query name, class and type. Those are guessable facts, not secrets, and the protocol's security comes down to making them hard to guess.
Weakness classes, as concepts
This section explains what goes wrong and why the design allows it. It is drawn from the security considerations in the RFCs above. For a broader catalogue, see DNS attack techniques and defenses.
Forged answers and cache poisoning
An off path attacker cannot see a resolver's query but can try to get a forged reply accepted. RFC 5452 lists what the forgery must match (question, ID, addresses and port) and notes it must arrive before the genuine answer. Because an attacker can often trigger a query just by asking, RFC 5452 warns that "any constant or sequential state held by such a resolver can be measured".
With a fixed source port, the only secret is the 16 bit ID, small enough that many guesses during the response window can succeed, and a forgery carrying referral data can poison a whole delegation. CVE-2008-1447, published on 8 July 2008, describes exactly this class: implementations that allowed "a birthday attack that uses in-bailiwick referrals to conduct cache poisoning against recursive resolvers", related to insufficient randomness.
The defences in RFC 5452 section 9.2 are now baseline requirements: an unpredictable source port drawn from as large a range as practicable, multiple ports for concurrent queries, an unpredictable ID across the full 0 to 65535 range, and acceptance of only in bailiwick records. Unbound's experimental use-caps-for-id option adds randomised letter case in query names; it defaults to no. The structural fix is DNSSEC validation, which makes forged data detectable regardless of how it arrived. TCP, cookies and encrypted transports also remove the blind guessing window on the hops they protect.
Open resolvers, reflection and amplification
A DNS response is often much larger than its query and UDP does not verify source addresses, so a resolver that answers anyone can be made to send large answers to a spoofed victim. RFC 5358 (BCP 140, October 2008) recommends operators "provide recursive name lookup service to only the intended clients", using source address access lists, interface selection, or TSIG and SIG(0) signed queries.
Authoritative servers must answer everyone, so they reduce the gain instead: minimal ANY answers (RFC 8482), minimal responses and response rate limiting. BIND's rate-limit statement exists, in its own words, "to prevent BIND 9 from being used to amplify reflection denial-of-service (DoS) attacks".
NXDOMAIN floods and resource exhaustion
Queries for random, never repeated subdomains bypass the cache, so each one sends the resolver to the target's authoritative servers, tying up resolver memory and loading the authority. The design allows it because a resolver cannot know a name does not exist until it asks.
The mitigations are layered: aggressive use of NSEC records from signed zones (RFC 8198) to answer nonexistent names from cache, NXDOMAIN cut handling (RFC 8020), caching of resolution failures (RFC 9520), per zone or per server outbound rate limits (Unbound's ratelimit option, which its documentation says "stops recursive floods, eg. random query names, but not spoofed reflection floods"), and limits on concurrent recursive clients.
Fragmentation and middlebox weaknesses
Only the first IP fragment of a large UDP answer carries the DNS header and ID; later fragments are matched by weaker IP reassembly rules. RFC 9715 notes that "IP fragmentation has exposed weaknesses in application protocols" and recommends responses within 1400 bytes and that requesters drop fragmented DNS responses.
Privacy leakage
Plain DNS exposes every query to the local network, the ISP, transit networks and, without minimisation, root and TLD operators. EDNS Client Subnet sends part of the client's address upstream, and the resolver operator always sees everything. QNAME minimisation reduces what upstream servers learn; encrypted DNS protects one hop; padding (RFC 7830) hides message lengths inside encrypted transports. None of these hides queries from the resolver you choose to trust.
Other classes worth knowing
Unrestricted zone transfers disclose every name in a zone. Changed NS records at the registrar hijack a domain while DNS works exactly as designed, so registry locks and account security are the controls. Dangling CNAME or NS records let whoever claims the abandoned resource serve content under your name. Rebinding, where a name flips from a public to an internal address, is covered in the attack techniques post.
Secure configuration for resolvers and authoritative servers
The following examples use BIND 9.20 and Unbound 1.26 option names checked against their official documentation. Addresses are from the documentation ranges (192.0.2.0/24 and 2001:db8::/32); replace them with your own networks.
A recursive resolver in BIND 9
In BIND 9.20, recursion defaults to yes, and if you set neither allow-recursion nor allow-query-cache, recursion is limited to localnets and localhost. dnssec-validation defaults to auto, using the built in root trust anchor. qname-minimization defaults to relaxed. edns-udp-size and max-udp-size both default to 1232. Being explicit is still worth it, because defaults change across versions and a single broad ACL elsewhere in the file can open the server.
acl "internal" { 192.0.2.0/24; 2001:db8::/32; localhost; };
options {
recursion yes;
allow-recursion { internal; };
allow-query-cache { internal; };
allow-transfer { none; };
dnssec-validation auto;
qname-minimization relaxed;
edns-udp-size 1232;
max-udp-size 1232;
minimal-any yes;
version none;
hostname none;
};
Per the BIND reference, version none disables processing of version.bind queries, and minimal-any yes answers ANY over UDP with a single RRset. Serve stale (stale-answer-enable) is off by default; turn it on only if you accept serving expired data during an outage.
A recursive resolver in Unbound
Unbound is restrictive by default: per its documentation, only localhost is allowed, and anything without a matching access-control rule is refused. It listens on localhost unless you add interfaces. qname-minimisation, harden-glue, aggressive-nsec and minimal-responses all default to yes, and edns-buffer-size defaults to 1232.
server:
interface: 192.0.2.53
interface: 2001:db8::53
access-control: 0.0.0.0/0 refuse
access-control: ::0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 192.0.2.0/24 allow
access-control: 2001:db8::/32 allow
auto-trust-anchor-file: "/var/lib/unbound/root.key"
harden-dnssec-stripped: yes
harden-glue: yes
aggressive-nsec: yes
qname-minimisation: yes
edns-buffer-size: 1232
minimal-responses: yes
deny-any: yes
hide-identity: yes
hide-version: yes
unwanted-reply-threshold: 10000000
Notes from the Unbound reference: hide-identity and hide-version default to no and, when enabled, make id.server, hostname.bind, version.server and version.bind queries return REFUSED. unwanted-reply-threshold counts unexpected replies and, when the threshold is reached, clears the caches and logs a warning; the documentation suggests 10 million. deny-any answers ANY with an empty response. use-caps-for-id is experimental and off by default; test it before enabling. ratelimit and ip-ratelimit are marked experimental.
An authoritative server in BIND 9
An authoritative only server should not recurse at all, should transfer zones only to its secondaries and should rate limit responses:
options {
recursion no;
allow-transfer { none; };
minimal-any yes;
version none;
rate-limit {
responses-per-second 10;
window 5;
};
};
zone "example.com" {
type primary;
file "/var/named/example.com.db";
allow-transfer { key "xfer-key"; };
};
The rate-limit values above are illustrative, not defaults; the BIND reference documents the full set of knobs (responses-per-second, nxdomains-per-second, errors-per-second, slip, window, exempt-clients and the IPv4 and IPv6 prefix lengths). Start in log-only mode and tune from what you see. With recursion no, BIND's allow-query-cache default becomes none, so the cache cannot be read either.
Hardening checklist
- Recursion only for your own clients, on servers separate from your authoritative service.
- DNSSEC validation on, with an automatically maintained root trust anchor, and QNAME minimisation on.
- UDP payload limits of 1232, never above 1400, and TCP port 53 open wherever UDP port 53 is.
- Zone transfers only to named secondaries with TSIG; version and identity queries disabled on public servers.
- Response rate limiting on authoritative servers; outbound limits and failure caching on resolvers.
- Encrypted DNS on the stub to resolver hop for clients that leave your network, and patched DNS software and DNS client libraries everywhere.
Checking a resolver and a zone with dig, delv and kdig
Only run these commands against systems you own or are authorised to test. Every option below is documented in the BIND 9.20 manual pages (dig, delv, named-checkconf, rndc), the Unbound 1.26 manual pages (unbound-checkconf, unbound-control) or the Knot DNS 3.6 kdig manual page.
Validate the configuration before you reload
# BIND: syntax check of named.conf
named-checkconf /etc/named.conf
# Unbound: syntax check, then print one effective option value
unbound-checkconf /etc/unbound/unbound.conf
unbound-checkconf -o qname-minimisation /etc/unbound/unbound.conf
# Unbound, running server: status and a live option value
unbound-control status
unbound-control get_option access-control
# BIND, running server: is serve stale on, and with which TTLs?
rndc serve-stale status
Is the resolver open?
From a host outside your network, ask your resolver's public address to resolve a name it should not resolve for strangers. A correctly closed resolver answers with status REFUSED or does not answer.
dig @198.51.100.53 example.com A
Look at the header line of the output. status: REFUSED with no ra flag is what you want. status: NOERROR with an answer and the ra flag from an outside address means you are running an open resolver.
Does it validate DNSSEC?
# Ask for DNSSEC records; a validating resolver sets the ad flag
dig @192.0.2.53 example.com A +dnssec +multiline
# delv validates locally and tells you why an answer is or is not secure
delv @192.0.2.53 example.com A
# Trace the resolution from a cold cache, logging every iterative query
delv +ns example.com A
In dig's flags line, ad means the resolver validated the answer. delv performs the validation itself and reports whether the answer validated and, if not, why. The delv +ns mode instantiates a full resolver and, per its manual page, "much more accurately replicates the behavior of a recursive name server with a cold cache" than dig +trace.
Does TCP work, and what is the UDP buffer size?
# Force TCP; this should succeed on both resolvers and authoritative servers
dig @192.0.2.53 example.com SOA +tcp
# Show the query dig sent and the OPT record in the reply
dig @192.0.2.53 example.com DNSKEY +dnssec +qr
# Request with a specific advertised buffer size
dig @192.0.2.53 example.com DNSKEY +dnssec +bufsize=1232
The OPT pseudosection of the reply shows the UDP payload size the server advertises. If a large answer comes back with the tc flag and the +tcp query succeeds, truncation and fallback are working as designed.
Cookies, NSID and version hiding
# dig sends a client cookie by default; look for COOKIE in the reply's OPT section
dig @192.0.2.53 example.com A +cookie
# Ask which server instance answered (useful behind anycast or load balancers)
dig @192.0.2.53 example.com A +nsid
# Should be REFUSED or empty on a hardened public server
dig @192.0.2.53 version.bind TXT CH
Zone checks on your authoritative servers
# Walk the delegation from the root to see each referral
dig +trace www.example.com A
# Ask an authority directly, without recursion; expect the aa flag
dig @ns1.example.com www.example.com A +norecurse
# Confirm zone transfers are refused to unauthorised hosts (run from a host that should NOT be allowed)
dig @ns1.example.com example.com AXFR
# Inspect service binding and CAA records
dig example.com HTTPS
dig example.com CAA
Encrypted transports with dig and kdig
# DNS over TLS with dig; port defaults to 853
dig @192.0.2.53 example.com A +tls
# DNS over TLS with certificate and hostname checks in kdig
kdig @192.0.2.53 +tls-ca +tls-hostname=resolver.example.net example.com A
# DNS over HTTPS
kdig @192.0.2.53 +https example.com A
# DNS over QUIC
kdig @192.0.2.53 +quic example.com A
kdig's +tls on its own uses the opportunistic privacy profile from RFC 7858, which encrypts but does not authenticate the server. Use +tls-ca with +tls-hostname when you want the strict behaviour clients should use.
Where DNSSEC, DoT, DoH and DoQ fit
These four are often treated as alternatives. They solve different problems, and our DoH versus DoT versus DNSSEC post goes deeper on the trade offs.
| DNSSEC | DNS over TLS (DoT) | DNS over HTTPS (DoH) | DNS over QUIC (DoQ) | |
|---|---|---|---|---|
| Standard | RFC 4033 to 4035 (March 2005) | RFC 7858 (May 2016) | RFC 8484 (October 2018) | RFC 9250 (May 2022) |
| Protects | Integrity and origin of data, authenticated denial | Confidentiality and integrity of one hop | Confidentiality and integrity of one hop | Confidentiality and integrity of one hop |
| Scope | End to end, zone to validating resolver | Client to resolver (or other hop) | Client to resolver | Stub to resolver, resolver to authoritative, zone transfer |
| Transport and port | Any DNS transport | TCP 853 | HTTPS | UDP 853 |
| Hides queries from the network | No | Yes | Yes, and blends with web traffic | Yes |
| Stops a lying resolver | Yes, if the client validates | No | No | No |
| Operational cost | Key management, signing, DS at the parent, larger answers | TLS certificates, connection state | HTTP stack, certificates; can bypass enterprise DNS policy | QUIC stack, certificates; newer client support |
For DNSSEC deployment details, see how to configure DNSSEC and what DNSSEC protects and what it does not. The short rule: sign your zones and validate on your resolvers, then encrypt the hops that leave networks you control.
When to use which DNS setup
| Use case | Recommended setup | Security properties | Operational cost |
|---|---|---|---|
| Internal resolver for an office or data centre | Recursive resolver with ACLs, DNSSEC validation, QNAME minimisation, plain DNS on the LAN | Integrity via DNSSEC; LAN traffic readable by insiders | Low |
| Laptops and phones on untrusted networks | Stub to resolver over DoT, DoH or DoQ, with the resolver validating DNSSEC | Confidentiality on the risky hop; integrity if validated | Medium: client configuration, certificates |
| Public authoritative DNS for your domains | Authoritative only servers, DNSSEC signed zones, RRL, minimal ANY, TSIG for transfers | Integrity for validating clients; reduced reflection | Medium: key rollovers, monitoring |
| Resolver to authoritative privacy | QNAME minimisation now; DoQ to authorities where both sides support it | Upstream sees less; encryption where supported | Low for minimisation; higher for DoQ |
| Zone distribution between your servers | AXFR or IXFR over TCP with TSIG, or over TLS or QUIC where supported | Authenticated transfers; encryption if TLS or QUIC | Low |
Common mistakes
- Running an open resolver by accident. A cloud VM with a resolver bound to its public interface and a broad ACL is the classic case. Check from outside, as above.
- Blocking TCP port 53. It breaks large answers, DNSSEC and zone transfers, and RFC 9210 makes allowing it a Best Current Practice.
- Advertising a 4096 byte UDP buffer. Large buffers invite fragmentation, which some networks drop and which weakens spoofing resistance. Use 1232 or at most 1400.
- Treating the AD bit as proof on an untrusted path. The AD bit is just a header flag. A client should trust it only if the path to its validating resolver is secure, or validate locally.
- Assuming DoH or DoT replaces DNSSEC. Encryption protects one hop. A compromised or misled resolver still returns wrong data over a perfectly encrypted channel.
- Mixing authoritative and recursive service on one public address. It exposes your cache to the Internet and makes ACLs harder to reason about.
- Leaving dangling CNAME and NS records. Remove DNS records before you decommission the resource they point to.
- Ignoring DNS libraries inside applications. Resolver libraries in runtimes and base images parse untrusted messages too and need patching like the server.
- Using strict QNAME minimisation without testing. Unbound's documentation warns that strict mode leaves "a lot of domains" unresolvable; the relaxed defaults exist for a reason.
FAQ
What port does DNS use?
Classic DNS uses port 53 over both UDP and TCP (RFC 1035). DNS over TLS uses TCP port 853 (RFC 7858), DNS over QUIC uses UDP port 853 (RFC 9250) and DNS over HTTPS runs inside ordinary HTTPS (RFC 8484).
Does DNS use TCP or UDP?
Both. Most queries use UDP because it is a single round trip. TCP is used for zone transfers, for answers too large for UDP (signalled by the TC bit) and by any client that chooses it. RFC 7766 requires every general purpose implementation to support both.
Is DNS encrypted?
Not by default. Plain DNS on port 53 is readable and alterable by anyone on the path. DoT, DoH and DoQ encrypt the hop between a client and the server it talks to. DNSSEC signs data but does not encrypt it.
What is the maximum size of a DNS message?
RFC 1035 limits UDP messages without EDNS to 512 bytes. With EDNS(0) a client can advertise a larger buffer; current guidance in RFC 9715 recommends no more than 1400 bytes and BIND and Unbound default to 1232. Over TCP the two byte length prefix allows messages up to 65,535 bytes.
What do the DNS header flags mean?
QR marks a response, AA an authoritative answer, TC a truncated message, RD a request for recursion, RA that recursion is available, AD that the resolver validated the answer with DNSSEC and CD that the client asked it not to validate. The 4 bit RCODE gives the result, such as NoError, NXDomain or ServFail.
What is EDNS(0)?
EDNS(0), defined in RFC 6891, extends DNS by adding an OPT pseudo record to the additional section. It carries the client's UDP buffer size, the DO flag and options such as cookies and padding.
What is QNAME minimisation?
A privacy technique from RFC 9156 where a resolver sends each upstream server only as much of the name as it needs to give a referral, rather than the full name and type.
DNS over HTTPS vs DNS over TLS: which is more secure?
They provide the same cryptographic protection for the hop they cover. DoT runs on its own port, which makes it easy for network operators to see and manage. DoH runs inside HTTPS, which hides it among web traffic but can bypass enterprise DNS controls. Neither authenticates the data the resolver returns; DNSSEC does that.
How do I check if my DNS resolver is an open resolver?
From a network outside your own, run dig against the resolver's public address for any name. If it returns an answer with the ra flag, it is open. A correctly configured resolver returns REFUSED or does not respond. Only test resolvers you operate.
Where Safeguard fits
Safeguard does not sit in your DNS path, inspect queries or act as a resolver. It covers the software that implements DNS inside what you build and ship: resolver libraries in runtimes, DNS client packages pulled in transitively, and DNS servers baked into container images. Software composition analysis with a full transitive inventory, SBOM generation in SPDX and CycloneDX, container scanning, vulnerability management with EPSS, CISA KEV and reachability, and policy gates are all generally available, so you can find which projects carry a vulnerable DNS library and stop it shipping again. It does not validate DNSSEC, close open resolvers or configure BIND or Unbound for you; the steps above are yours to run.
Fix it without waiting for upstream
DNS weaknesses that reach production usually live in a library or daemon your software depends on: a resolver library in a runtime, a DNS client package several levels down your dependency tree, or the DNS server shipped in a base image. That is exactly the kind of exposure the steps below are built for.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show dependency and SBOM workflows, not DNS monitoring. You can download Safeguard and follow the CLI docs to try them on your own projects.
This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at a container image that bundles a DNS server or forwarder, the same scan lists the resolver packages and DNS client libraries inside it with their versions and any known vulnerabilities, so you know which DNS code you are actually running.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the package to bump is a DNS client library pinned in your manifest.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects include a particular resolver library version, and get the answer from your own scan data rather than from memory.
Click through it at your own pace, or open it full screen.
Quick check
Your resolver returns answers with the ad flag set. Does that mean a client on public Wi-Fi can trust them?
Only if the path between the client and the resolver is protected. The AD bit is a single header flag, and plain DNS has no integrity protection, so anyone on that path could set it. Use DoT, DoH or DoQ to the validating resolver, or validate DNSSEC on the client itself.
Why do modern resolvers advertise a 1232 byte EDNS buffer instead of 4096?
Large UDP answers fragment, and later fragments carry no DNS header, so they are matched only by weaker IP reassembly rules and are often dropped by middleboxes. 1232 bytes fits within the IPv6 minimum MTU of 1280 after headers. Anything larger is truncated and retried over TCP, which RFC 7766 makes mandatory to support.
Sources
- RFC 1034: Domain Names, Concepts and Facilities (November 1987)
- RFC 1035: Domain Names, Implementation and Specification (November 1987)
- RFC 1123: Requirements for Internet Hosts, Application and Support
- RFC 2181: Clarifications to the DNS Specification
- RFC 3596: DNS Extensions to Support IP Version 6
- RFC 4033: DNS Security Introduction and Requirements
- RFC 5358 (BCP 140): Preventing Use of Recursive Nameservers in Reflector Attacks
- RFC 5452: Measures for Making DNS More Resilient against Forged Answers
- RFC 6891 (STD 75): Extension Mechanisms for DNS (EDNS(0))
- RFC 6895 (BCP 42): DNS IANA Considerations
- RFC 7766: DNS Transport over TCP, Implementation Requirements
- RFC 7858: Specification for DNS over Transport Layer Security
- RFC 7873: Domain Name System (DNS) Cookies
- RFC 8020: NXDOMAIN, There Really Is Nothing Underneath
- RFC 8198: Aggressive Use of DNSSEC-Validated Cache
- RFC 8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY
- RFC 8484: DNS Queries over HTTPS (DoH)
- RFC 8659: DNS Certification Authority Authorization (CAA) Resource Record
- RFC 8767: Serving Stale Data to Improve DNS Resiliency
- RFC 9018: Interoperable Domain Name System (DNS) Server Cookies
- RFC 9156: DNS Query Name Minimisation to Improve Privacy
- RFC 9210 (BCP 235): DNS Transport over TCP, Operational Requirements
- RFC 9250: DNS over Dedicated QUIC Connections
- RFC 9460: Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)
- RFC 9499 (BCP 219): DNS Terminology
- RFC 9520: Negative Caching of DNS Resolution Failures
- RFC 9715: IP Fragmentation Avoidance in DNS over UDP
- IANA: Domain Name System (DNS) Parameters
- CVE-2008-1447 record
- BIND 9 Administrator Reference Manual: Configuration Reference
- BIND 9 Manual Pages: dig, delv, named-checkconf, rndc
- Unbound documentation: unbound.conf(5)
- Unbound documentation: unbound-control(8)
- Unbound documentation: unbound-checkconf(8)
- Knot DNS documentation: kdig
Last reviewed: 23 October 2026. Defaults quoted are from BIND 9.20, Unbound 1.26 and Knot DNS 3.6 documentation; check the documentation for the version you run.