DNS fundamentals: refresher
A focused refresher of the parts of DNS you need to make sense of the rest of this wiki. If you already know what an authoritative server does, what AA/AD/RA mean in a packet header, and why TTLs cut both ways, skip ahead to The dig-along or nslookup and dig.
What DNS actually is#
A distributed key-value lookup. The key is a name (example.com). The value is one or more resource records typed by class (IN) and type (A, AAAA, MX, TXT, …). The lookup happens via a hierarchy of servers that each know one piece of the answer or know who does.
See record-types for the full record-type index.
The resolver dance#
A client asks a recursive resolver. The resolver walks the hierarchy until it finds an authoritative answer, then returns it.
client recursive resolver root -> TLD -> authoritative
------ ------------------ ----------------------------
"what is +------------------+ +-----------------------------+
example.isitdns.net?" ->| 1. ask root |---->| "ask the .net NS" |
| | +-----------------------------+
| 2. ask .net |---->+-----------------------------+
| | | "ask isitdns.net NS: |
| | | coleman.ns.cloudflare.com" |
| | +-----------------------------+
| 3. ask |---->+-----------------------------+
| coleman.ns. | | "192.0.2.1" (illustrative) |
| cloudflare.com | +-----------------------------+
| 4. cache it |
| 5. return it |
+--------+---------+
|
v
192.0.2.1A single DNS server can play both roles depending on the question:
- Authoritative for the zones it hosts: it owns the records and sets
aa=1in the response flags. - Recursive for everything else: when a client asks for a name outside its own zones, it walks the hierarchy on the client's behalf, then caches.
See anatomy-of-a-query for a step-by-step walk of the full resolution path.
Try it. Query the NS records for this site via a public recursive resolver:
dig @1.1.1.1 isitdns.net NS;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62408
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
isitdns.net. 86118 IN NS hera.ns.cloudflare.com.
isitdns.net. 86118 IN NS coleman.ns.cloudflare.com.rd (recursion desired) and ra (recursion available) are both set: you asked a recursive resolver and it walked the hierarchy on your behalf. You also get ad here because the zone is DNSSEC-signed and 1.1.1.1 validated it (covered in the flags section below).
nslookup -type=NS isitdns.net 1.1.1.1nslookup can show the NS answer but does not expose header flags like
rd,ra, oraa. Use dig to inspect flags.
Windows (PowerShell):
Resolve-DnsName -Name isitdns.net -Type NS -Server 1.1.1.1Record types you'll see in the logs#
| Type | What it stores | Example |
|---|---|---|
A | IPv4 address | ns1.example.com A 192.0.2.53 |
AAAA | IPv6 address | ns1.example.com AAAA 2001:db8:101::53 |
CNAME | Alias to another name | www.example.com CNAME example.com |
MX | Mail server | example.com MX 10 mail.example.com |
NS | Authoritative nameserver for a zone | isitdns.net NS coleman.ns.cloudflare.com |
SOA | Zone metadata: serial, refresh, expire | isitdns.net SOA coleman.ns.cloudflare.com. dns.cloudflare.com. 2409741473 … |
TXT | Free text: heavily used for SPF, DKIM, verification | example.com TXT "v=spf1 -all" |
PTR | Reverse: IP to name | 53.2.0.192.in-addr.arpa PTR ns1.example.com |
SRV | Service location with priority and port | _sip._tcp.example.com SRV 0 5 5060 sip.example.com |
CAA | Which CAs may issue certs for this zone | example.com CAA 0 issue "letsencrypt.org" |
DS / DNSKEY / RRSIG | DNSSEC chain-of-trust glue | (see dnssec section below) |
When you watch a DNS logging stream and filter by qtype, this is the alphabet you're working with.
Try it. Pull a live A record from the demo zone:
dig @1.1.1.1 example.isitdns.net A;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41515
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
example.isitdns.net. 300 IN A 192.0.2.1nslookup -type=A example.isitdns.net 1.1.1.1The A record is 192.0.2.1, a documentation-range address (RFC 5737). It demonstrates the record structure without routing to a real host.
Pull a TXT record:
dig @1.1.1.1 txt.example.isitdns.net TXT;; ANSWER SECTION:
txt.example.isitdns.net. 300 IN TXT "It was DNS. It is always DNS."nslookup -type=TXT txt.example.isitdns.net 1.1.1.1The flags in the DNS header#
A DNS message header packs a row of single-bit flags that tell you, at a glance, what kind of message this is. The ones you'll see most in query logs:
| Flag | Set when | Means |
|---|---|---|
qr | Response (not query) | qr=false is a question frame. qr=true is an answer frame. |
aa | Authoritative | The responder owns this zone. Always check this before trusting an answer is canonical. |
tc | Truncated | UDP answer did not fit in the packet. Client should retry over TCP. |
rd | Recursion desired | Client wants the resolver to do the walking. Set on virtually every stub-resolver query. |
ra | Recursion available | Server is willing to recurse. Authoritative-only servers should NOT have this set. |
ad | Authenticated data | Resolver claims DNSSEC validation succeeded. Not set by all public resolvers even when validation passed; use RRSIG presence as the primary signal. |
cd | Checking disabled | Client is asking the resolver NOT to validate DNSSEC. Rare. |
When something is wrong, the flags tell you almost as much as rcode. An aa=1 answer with unexpected rdata means a misconfiguration in the zone, not the resolver. Missing RRSIG records for a zone you signed means DNSSEC is broken upstream (a validating resolver will return SERVFAIL for that zone).
Try it. See flags in practice. Query a public recursive resolver:
dig @1.1.1.1 isitdns.net NS;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1rd (recursion desired) and ra (recursion available) are both set: you asked a recursive resolver to recurse, and it did. ad is set because the zone is signed and 1.1.1.1 validated the answer. The aa flag is absent because 1.1.1.1 is not the authoritative server for isitdns.net, it is caching and forwarding the answer.
To see aa, ask a server that owns the zone. Query one of the zone's own NS directly, with recursion off:
dig @coleman.ns.cloudflare.com isitdns.net SOA +norec;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
isitdns.net. 1800 IN SOA coleman.ns.cloudflare.com. dns.cloudflare.com. 2409741473 10000 2400 604800 1800aa is set and ra is absent: the server owns the zone and does not offer recursion. (The serial will differ when you run it; it bumps on every zone change. And on a network that intercepts port 53, a middlebox may answer instead of coleman: see the interception note in the rcode section.)
Confirm the ad bit via DoH, which bypasses any local stub that might strip it:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=isitdns.net&type=A'{"Status":0,...,"AD":true,...}AD: true means the validating resolver at cloudflare-dns.com confirmed DNSSEC signatures for this answer.
nslookup does not display response flags. Use dig or curl DoH to inspect
ra,aa, andad.
rcode: the answer's verdict#
| rcode | Meaning | When you see it |
|---|---|---|
NOERROR | Success | Normal answer. |
NXDOMAIN | Name does not exist | The most common "interesting" answer: could be a typo, a name caught by a policy filter, or a real misconfiguration. |
SERVFAIL | Server failure | Upstream broken, DNSSEC validation failed, recursion timed out. Always investigate. |
REFUSED | Server won't answer | Client outside the ACL, or recursion turned off and the question was not for an owned zone. |
FORMERR | Malformed query | Client bug or unusual EDNS option the server does not understand. |
NOTIMP | Not implemented | The query type or opcode is not supported. The everyday sighting: 1.1.1.1 answers ANY queries with NOTIMP, since RFC 8482 lets operators decline ANY. |
Try it. Cause a deliberate NXDOMAIN by querying a name that does not exist in a real zone:
dig @1.1.1.1 definitely-does-not-exist.iana.org A;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 15531
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; AUTHORITY SECTION:
iana.org. 3419 IN SOA sns.dns.icann.org. noc.dns.icann.org. ...The SOA record in the authority section is the negative proof: the authoritative server says the zone exists but that name does not.
nslookup definitely-does-not-exist.iana.org 1.1.1.1The same aa pattern from the flags section works against any conventional authoritative server. This site's demo authoritative, pns1.isitdns.net, answers +norec for the probe.isitdns.net SOA the same way:
Note: the demo authoritative server for
probe.isitdns.netis being rebuilt, so these specific lookups may not answer right now. The mechanics and the output shown are exactly what a healthy conventional authoritative server returns.
On a network that intercepts port 53 (captive portals, some home routers), a
@serverquery is answered by the interceptor instead of the named server. If you see onlyrd rawithoutaa, that is your network, not pns1. Use a trusted vantage or DoH to confirm.
dig @pns1.isitdns.net probe.isitdns.net SOA +norec;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
probe.isitdns.net. 300 IN SOA pns1.isitdns.net. hostmaster.isitdns.net. 2026061511 7200 3600 1209600 300aa is set because pns1 is the authoritative source for probe.isitdns.net. rd and ra are absent: neither requested nor offered. The nslookup equivalent:
nslookup -norecurse -type=SOA probe.isitdns.net pns1.isitdns.netCause a REFUSED by sending a query with the recursion-desired bit off to a resolver whose policy is to refuse such queries. Quad9 is one:
dig @9.9.9.9 google.com A +norec;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: ...
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1nslookup -norecurse google.com 9.9.9.9REFUSED is a policy answer: the server is declining the query, classically because it is not authoritative for the name and was not asked to recurse. Quad9 is anycast, so the OPT pseudosection details vary by which node answers: some nodes attach an extended DNS error (EDE 20, Not Authoritative) and advertise a 1232-byte UDP buffer, others attach no EDE and advertise 512, so do not rely on those specific lines. Policy varies by resolver. The same +norec query gets NOERROR with a full answer section from 1.1.1.1, which happily serves it from cache, and SERVFAIL from 8.8.8.8. The rd bit is only a request for recursion, not an authorization gate; what a server does when it is absent is that server's choice.
nslookup can show the rcode in its output, but labeling it as "REFUSED" or "NXDOMAIN" depends on the platform. dig always prints the rcode name in the header line. See dnssec-troubleshooting for patterns around
SERVFAIL.
TTL: the double-edged knob#
A record's TTL is how long resolvers may cache the answer before re-asking authoritative.
- Short TTL (60s): changes propagate fast, but every cache miss is another query to the authoritative server. Authoritative load scales inversely with TTL.
- Long TTL (24h): cheap on authoritative, but if you make a mistake everyone sees the old answer for a day.
- Standard production: 3600 (1h) for stable records, 300 (5min) before a planned change, then raise back.
DNS logging tools surface the TTL in the answer section of every client response. TTL is one of the easiest things to forget to set deliberately.
Try it. The demo A record carries a 300-second TTL. Query it twice and watch the TTL count down:
dig @1.1.1.1 example.isitdns.net A +noall +answerexample.isitdns.net. 300 IN A 192.0.2.1Run it again within 5 minutes and the TTL will be lower, because 1.1.1.1 is serving from cache.
nslookup -type=A example.isitdns.net 1.1.1.1nslookup does not show TTL in its default output (
nslookup -debugdoes). Use dig to observe the remaining cache lifetime.
Transports: the modern landscape#
Classic DNS used UDP on port 53, falling back to TCP when answers got large. That is still the floor. Stacked on top:
| Transport | Port | What it adds | Trade-off |
|---|---|---|---|
| Do53 / UDP | 53 | Original. Tiny overhead. | Cleartext. Anyone on the path sees every query. |
| Do53 / TCP | 53 | Used for large answers (tc=1), zone transfers (AXFR/IXFR) | Still cleartext. |
| DoT (DNS over TLS) | 853 | Encrypts the query/response | One TCP+TLS connection per session. Easy to fingerprint by port. |
| DoH (DNS over HTTPS) | 443 | Encrypts and blends with HTTPS traffic; the destination IP and the TLS SNI (absent ECH) can still fingerprint it, but the URI path and queries are inside the encryption | Costs more CPU; harder for network operators to inspect (which is sometimes the point). |
| DoQ (DNS over QUIC) | 853 | DoT, but on QUIC: faster session setup | Newer; uptake still building. |
DNS logging tools surface the transport (UDP or TCP) and the destination port (53, 853, or 443), which together identify the protocol in use.
Try it. The dig examples above all use UDP by default. Force TCP with +tcp:
dig @1.1.1.1 isitdns.net NS +tcp;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
isitdns.net. 86400 IN NS hera.ns.cloudflare.com.
isitdns.net. 86400 IN NS coleman.ns.cloudflare.com.The answer is identical to the UDP result. The transport changed; the data did not. That sameness is the point: +tcp switches the wire protocol without affecting what the resolver returns. The nslookup equivalent:
nslookup -vc isitdns.net 1.1.1.1The -vc flag forces a virtual circuit, i.e. TCP.
nslookup does not support DoH or DoT. Modern dig (BIND 9.18+) speaks both:
+tlsfor DoT and+httpsfor DoH. For DoQ usekdigfrom theknot-dnsutilspackage, and curl works for DoH as shown below.
Send the same query as DoH with curl:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=isitdns.net&type=NS'See dot-doh for a deeper look at encrypted DNS transports.
DNSSEC, briefly#
A chain-of-signatures from the root down to each leaf record. A validating resolver:
- Asks for the record (e.g.
isitdns.net A). - Asks for the
RRSIG(signature) over that answer. - Asks for the zone's
DNSKEY, verifies the signature. - Asks the parent zone for the
DSrecord that fingerprints the child'sDNSKEY, verifies the chain. - Returns the record to the client.
If any step fails: SERVFAIL. There is no "soft fail" by design: DNSSEC's job is to refuse to lie.
The reliable signals that validation is working are RRSIG presence in the answer section and the canary test: query dnssec-failed.org with +dnssec; a validating resolver returns SERVFAIL, and re-querying with +cd (checking disabled) returns the answer. The ad bit is set by a validating resolver when the query asks for it (modern dig sets the AD bit in queries by default; +dnssec also requests it), but a middlebox on the path to your stub can strip it, so ad alone is not a portable validation indicator.
isitdns.net is fully signed and anchored. A DS record (keytag 2371, ECDSAP256SHA256) exists at the .net parent, and validating resolvers return AD: true.
Try it. Query with DNSSEC enabled and confirm the RRSIG:
dig @1.1.1.1 isitdns.net A +dnssec;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
isitdns.net. 300 IN A 172.67.155.242
isitdns.net. 300 IN A 104.21.56.211
isitdns.net. 300 IN RRSIG A 13 2 300 20260718041001 20260716021001 34505 isitdns.net. (
8DnlQ9U563CNkYb7EOx754x... )The RRSIG is algorithm 13 (ECDSAP256SHA256). Its presence proves the zone is signed, and ad confirms 1.1.1.1 validated it. The inception/expiration timestamps and signature bytes will differ when you run it: Cloudflare re-signs on a rolling window, so those fields are always moving. If a local middlebox strips ad from port-53 answers, curl over DoH sees it reliably:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=isitdns.net&type=A'{"Status":0,...,"AD":true,...}AD: true confirms end-to-end DNSSEC validation.
Canary test. Confirm a validating resolver rejects a broken DNSSEC chain:
dig @1.1.1.1 dnssec-failed.org A +dnssec;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 38951
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1SERVFAIL with zero answers: validation failed. Re-query with +cd (checking disabled) to confirm the name exists but its DNSSEC chain is broken:
dig @1.1.1.1 dnssec-failed.org A +dnssec +cd;; flags: qr rd ra cd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
dnssec-failed.org. 293 IN A 96.99.227.255
dnssec-failed.org. 293 IN RRSIG A 13 2 300 ...The cd flag bypasses validation. The RRSIG is present but the resolver refused to validate it.
nslookup cannot inspect RRSIG records, the
adflag, or thecdflag. For DNSSEC diagnostics, use dig. See dnssec and dnssec-troubleshooting for the full chain-of-trust walkthrough.
Where to next#
- DNSSEC, signed and validated: the chain of trust in detail
- dnssec-troubleshooting: diagnosing
SERVFAILand broken chains - Anatomy of a query: every step from name to answer
- nslookup and dig: the two tools you will actually use
- The dig-along: a hands-on walkthrough using dig against live records
- record-types: the full record-type index
- IP address spaces: public vs private vs documentation IPs in DNS
- dot-doh: encrypted DNS transports (DoT, DoH, DoQ) in depth