
What Is EDNS, and Why Was It Introduced?
Run dig against almost any domain and you'll see a line you've probably skimmed past a hundred times: ; EDNS: version: 0, flags:; udp: 1232. That line is the visible part of EDNS, the extension mechanism that keeps a protocol designed in the 1980s working for DNSSEC, IPv6, geographic routing, and modern privacy features. Without it, DNS would still be stuck with 512-byte messages and no way to add new capabilities without breaking every server on the internet.
This article explains what EDNS is, the problems it was created to solve, how the OPT pseudo-record carries its data, the most important EDNS options you'll encounter, and how to inspect EDNS behavior with dig and Python. It pairs naturally with why DNS uses port 53 and when it switches to TCP, since EDNS is what lets most large answers stay on UDP.
The Problem: A Protocol With No Room to Grow
The original DNS message format from RFC 1035 was compact and rigid. Every message has a fixed 12-byte header, and nearly every bit in it was assigned from day one. That design caused three hard limits:
- 512-byte UDP messages. Responses larger than 512 bytes had to be truncated, forcing a retry over TCP with its extra round trips and server load.
- Only 4 bits for response codes. That allows 16 RCODE values, most of which were already used, leaving almost no room for new error types.
- No spare flag bits. There was nowhere in the header to signal new capabilities, like "I understand DNSSEC."
By the late 1990s, DNSSEC was being designed, and it needed exactly what DNS couldn't offer: much larger responses to carry keys and signatures, and a way for clients to say they wanted them. IPv6 added more pressure, since AAAA glue records are four times the size of A records.
Changing the header format would have broken every existing implementation. The solution had to be backward-compatible.
The Solution: Extension Mechanisms for DNS
EDNS (Extension Mechanisms for DNS, sometimes written EDNS0 for version 0) was first specified in RFC 2671 in 1999 and revised as RFC 6891 in 2013. Instead of changing the header, it adds a special record to the additional section of the message: the OPT pseudo-record (record type 41).
It's called a "pseudo" record because it never appears in a zone file and is never cached. It exists only for the duration of one query-response exchange, carrying metadata about the transport rather than data about a domain. An implementation that doesn't understand OPT records can simply ignore the additional section — which is what made EDNS deployable without a flag day.
The negotiation is simple:
- The client includes an OPT record in its query to say "I support EDNS, and here's how large a UDP response I can accept."
- A server that supports EDNS includes an OPT record in its response, with its own buffer size and any options it's answering.
- A server that doesn't support EDNS responds without an OPT record (or, if badly broken, with an error or no response), and the client falls back to plain DNS.
Inside the OPT Record
The OPT record reuses the standard resource record layout but repurposes several fields:
| RR field | Normal meaning | Meaning in OPT |
|---|---|---|
| NAME | Owner name | Always the root (.) |
| TYPE | Record type | 41 (OPT) |
| CLASS | Usually IN | Sender's maximum UDP payload size in bytes |
| TTL | Cache lifetime | Extended RCODE (8 bits), EDNS version (8 bits), flags (16 bits) |
| RDATA | Record data | Zero or more options, each with a code, length, and value |
That reuse unlocks everything EDNS offers:
- UDP payload size in the CLASS field lifts the 512-byte limit.
- Extended RCODE bits combine with the header's 4 bits to give 12-bit response codes, allowing values like
BADVERS(16) andBADCOOKIE(23). - Flags include the DO (DNSSEC OK) bit, defined in RFC 3225, which tells a server the client wants DNSSEC records.
- Options in RDATA are an open-ended list, so new features can be added by allocating a new option code without touching the format again.
Reading the OPT pseudosection in dig
dig decodes the OPT record into its own section:
dig +dnssec +norecurse example.com A @hera.ns.cloudflare.com
;; flags: qr aa; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
That line says the server speaks EDNS version 0, has echoed the DO flag because DNSSEC records were requested, and will accept UDP messages up to 1232 bytes. The ADDITIONAL: 1 count in the header is the OPT record itself.
UDP Buffer Sizes and DNS Flag Day 2020
EDNS lets clients advertise buffers up to 65,535 bytes, and early implementations commonly used 4096. That turned out to cause problems. A UDP response larger than the path's MTU — usually around 1500 bytes on Ethernet — gets split into IP fragments. Fragments are frequently dropped by firewalls, and fragmentation also opens the door to certain cache poisoning attacks.
DNS Flag Day 2020 coordinated a change across major resolver and server vendors to default to an EDNS buffer size of 1232 bytes. That value fits within the minimum IPv6 MTU of 1280 bytes after IPv6 and UDP headers, so responses avoid fragmentation on virtually any path. Anything larger gets truncated and retried over TCP.
You can test how a server handles different buffer sizes:
dig +bufsize=1232 +dnssec . DNSKEY @a.root-servers.net
dig +bufsize=512 +dnssec . DNSKEY @a.root-servers.net
The first fits in UDP. The second is truncated, and dig reports Truncated, retrying in TCP mode. before printing the full answer.
In BIND, you set the advertised and maximum sizes in named.conf:
options {
edns-udp-size 1232; // size advertised in outgoing queries
max-udp-size 1232; // largest UDP response this server will send
};
Recent BIND versions already default to 1232, so you'd only set these to deviate from it. Unbound uses edns-buffer-size: 1232, also the current default.
DNS Flag Day 2019: Ending the Workarounds
The first DNS Flag Day, in February 2019, dealt with a different EDNS problem. Some authoritative servers and firewalls responded badly to EDNS queries — dropping them, returning errors, or mangling the OPT record. To cope, resolvers had accumulated workarounds: if an EDNS query timed out, they'd retry without EDNS. That made every timeout ambiguous ("is the server down, or does it hate EDNS?") and slowed resolution for everyone.
On Flag Day 2019, major open source resolvers and public DNS operators removed those workarounds. Servers that couldn't handle EDNS correctly simply stopped resolving until their operators fixed them. It was a deliberate push to make correct EDNS support a baseline requirement — and it largely worked.
Important EDNS Options
EDNS options are identified by numeric codes registered with IANA. These are the ones you're most likely to meet:
| Code | Option | RFC | What it does |
|---|---|---|---|
| 3 | NSID | 5001 | Asks the server to identify which instance answered |
| 8 | Client Subnet (ECS) | 7871 | Passes part of the client's IP to authoritative servers for location-aware answers |
| 10 | COOKIE | 7873 | Lightweight client and server cookies to resist spoofing and amplification |
| 11 | TCP Keepalive | 7828 | Negotiates idle timeouts for persistent TCP connections |
| 12 | Padding | 7830 | Pads encrypted DNS messages to hide their size |
| 15 | Extended DNS Errors | 8914 | Adds a specific reason code and text to an error response |
NSID: which server answered?
Anycast means one IP address can be served by hundreds of machines. NSID asks the server to say which one you hit:
dig +nsid +norecurse . SOA @k.root-servers.net
; EDNS: version: 0, flags:; udp: 1232
; NSID: 6e 73 32 2e 62 68 2d 61 6d 68 2e 6b 2e 72 69 70 65 2e 6e 65 74 ("ns2.bh-amh.k.ripe.net")
The value is operator-defined but usually encodes the site and host. It's invaluable when debugging anycast DNS problems that only appear from certain locations.
Client Subnet: location-aware answers
When you use a public resolver, the authoritative server only sees the resolver's IP address, not yours. If that server does location-based routing, it might send you to a data center near the resolver rather than near you. EDNS Client Subnet (ECS) lets the resolver include a truncated version of your address — typically a /24 for IPv4 or /56 for IPv6 — so the authoritative server can tailor the answer.
dig +subnet=203.0.113.0/24 example.com A @8.8.8.8
dig sends the ECS option with the given prefix, and the response's OPT section echoes a CLIENT-SUBNET line showing the prefix and the scope the server used. ECS is a privacy trade-off: it reveals part of your network location to authoritative servers. Some resolvers send it only to servers that need it, and some, like Cloudflare's 1.1.1.1, don't send it at all. The GeoDNS post covers how authoritative servers use it.
Cookies: cheap spoofing protection
DNS cookies give clients and servers a lightweight way to recognize each other across queries. The client sends a random client cookie; the server returns a server cookie derived from the client's address and a secret. Later queries that carry a valid server cookie can be trusted not to come from a spoofed source, which helps servers decide when to apply rate limits. Modern versions of dig send cookies by default and print a COOKIE: line in the OPT section, marked (good) when the server's cookie validates. This complements, rather than replaces, defenses against DNS amplification attacks.
Extended DNS Errors: why did it fail?
A plain SERVFAIL tells you nothing about why a lookup failed. Extended DNS Errors (EDE) add a code and an optional text explanation. Ask a validating resolver for a domain with deliberately broken DNSSEC:
dig dnssec-failed.org A @1.1.1.1
Recent versions of dig print something like this:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 16510
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; EDE: 9 (DNSKEY Missing): (no SEP matching the DS found for dnssec-failed.org.)
EDE code 9 means no DNSKEY matched the DS record at the parent — a classic DNSSEC misconfiguration. Older dig builds that don't decode EDE show it as a raw OPT=15: line with the same text in parentheses. EDE turns many mystery SERVFAILs into immediately actionable messages, and it's worth checking first when you're dealing with NXDOMAIN, SERVFAIL, and REFUSED responses.
Working With EDNS in Python
dnspython exposes every part of EDNS. This script sends an EDNS query with the DO bit and an NSID request to a root server, then queries a validating resolver for a broken domain and prints the Extended DNS Error:
import dns.edns
import dns.flags
import dns.message
import dns.query
import dns.rcode
import dns.rdatatype
# 1. Ask a root server for its SOA with EDNS, the DO bit, and an NSID request
query = dns.message.make_query(
".", "SOA",
use_edns=0, # EDNS version 0
payload=1232, # advertised UDP buffer size
want_dnssec=True, # set the DO bit
options=[dns.edns.GenericOption(dns.edns.OptionType.NSID, b"")],
)
query.flags &= ~dns.flags.RD
response = dns.query.udp(query, "193.0.14.129", timeout=3) # k.root-servers.net
print("EDNS version:", response.edns)
print("Server UDP payload:", response.payload)
print("DO bit echoed:", bool(response.ednsflags & dns.flags.DO))
print("Answer types:", [dns.rdatatype.to_text(r.rdtype) for r in response.answer])
for option in response.options:
if option.otype == dns.edns.OptionType.NSID:
print("NSID:", option.to_wire().decode(errors="replace"))
# 2. Ask a validating resolver for a deliberately broken domain and read the EDE
query = dns.message.make_query("dnssec-failed.org", "A", use_edns=0, payload=1232)
response = dns.query.udp(query, "1.1.1.1", timeout=3)
print("\nRCODE:", dns.rcode.to_text(response.rcode()))
for option in response.options:
if isinstance(option, dns.edns.EDEOption):
print(f"Extended DNS Error {option.code}: {option.text}")
EDNS version: 0
Server UDP payload: 1232
DO bit echoed: True
Answer types: ['SOA', 'RRSIG']
NSID: ns2.bh-amh.k.ripe.net
RCODE: SERVFAIL
Extended DNS Error 9: no SEP matching the DS found for dnssec-failed.org.
Because the DO bit was set, the root server returned the SOA along with its RRSIG signature. The NSID value will vary depending on which anycast instance you reach. Install the library with pip install dnspython first.
Testing EDNS Compliance
If you run authoritative servers, check that they handle EDNS correctly. A compliant server should answer an unknown EDNS version with BADVERS rather than ignoring or dropping it:
dig +edns=1 +noednsnegotiation +norecurse example.com SOA @ns1.example.com
Look for status: BADVERS and an OPT record showing version: 0. Also confirm the server works without EDNS at all:
dig +noedns +norecurse example.com SOA @ns1.example.com
The response should contain no OPT pseudosection and still answer normally. ISC maintains an online EDNS compliance tester at ednscomp.isc.org that runs a full battery of these checks against a zone's nameservers.
EDNS FAQ
EDNS stands for Extension Mechanisms for DNS. Version 0, often written EDNS0, is defined in RFC 6891 and is the only version in use.
The original DNS format limited UDP messages to 512 bytes and had no spare header space for new flags or response codes. EDNS was introduced to allow larger messages and new features, most urgently to support DNSSEC.
The OPT record is a pseudo-record of type 41 added to the additional section of a DNS message. It carries the UDP payload size, extended RCODE bits, the EDNS version, flags like DO, and a list of options. It's never stored in zones or cached.
1232 bytes fits within the minimum IPv6 MTU of 1280 after IPv6 and UDP headers, so responses avoid IP fragmentation. DNS Flag Day 2020 made it the common default across major DNS software.
The DNSSEC OK bit is an EDNS flag a client sets to say it wants DNSSEC records such as RRSIG in the response. Servers only include those records when the bit is set.
ECS is an EDNS option that lets a recursive resolver pass a truncated form of the client's IP address to authoritative servers, so they can return location-appropriate answers. It improves CDN routing but reveals some location data.
Extended DNS Errors, defined in RFC 8914, add a numeric reason code and optional text to a response, explaining failures such as DNSSEC validation errors, blocked domains, or stale answers.
Virtually all modern DNS software does, and DNS Flag Day 2019 removed most resolver workarounds for servers that don't. Some very old or misconfigured servers and middleboxes still mishandle it.
Conclusion
EDNS solved a hard problem elegantly: how to extend a protocol with no spare room without breaking the millions of systems that already speak it. By tucking a pseudo-record into the additional section, it lifted the 512-byte UDP limit, expanded response codes, created space for flags like DO, and opened an extensible list of options that now carries NSID, client subnet, cookies, padding, and extended errors. DNSSEC as we know it would not be deployable without it.
For day-to-day work, the OPT pseudosection in dig output is worth reading rather than skipping. It tells you the buffer size in use, whether DNSSEC was requested, which anycast instance answered, and, increasingly, exactly why a lookup failed. If you operate DNS servers, keep the 1232-byte default, make sure TCP fallback works, and run an EDNS compliance check after any major change.
Here are some useful references for going deeper on EDNS:
- RFC 6891: Extension Mechanisms for DNS (EDNS(0)) — the current EDNS specification, including the OPT record format.
- RFC 8914: Extended DNS Errors — defines EDE codes and how they are carried.
- RFC 7871: Client Subnet in DNS Queries — the specification for EDNS Client Subnet.
- DNS Flag Day: dnsflagday.net — background on the 2019 EDNS workaround removal and the 2020 buffer size change.
- IANA: Domain Name System (DNS) Parameters — the registry of EDNS option codes and other DNS parameters.


