Software Engineering Wiki

Networking

DNS

Resolving names with dig, reading the answer, TTL and caching behaviour, search domains and Kubernetes resolution.

Cheatsheet #

TaskCommand
Resolve a namedig +short api.example.com
Which resolver answereddig api.example.com | grep SERVER
Bypass the local cachedig @1.1.1.1 api.example.com
Ask the authoritative serverdig +norecurse @ns1.example.com api.example.com
Full delegation pathdig +trace api.example.com
Reverse lookupdig -x 93.184.216.34 +short
Nameservers for a zonedig NS example.com +short
Mail recordsdig MX example.com +short
TXT (SPF, verification)dig TXT example.com +short
Zone serialdig SOA example.com +short
Does the resolver cache itdig api.example.com | grep -A1 'ANSWER SECTION' (TTL counts down)
What the system resolver doesresolvectl query api.example.com
In-cluster namekubectl run -it --rm d --image=busybox -- nslookup api.myns

A name that will not resolve #

Ask three questions in order: does the authoritative server have the record, does the recursive resolver return it, and does this host use that resolver.

dig +short api.example.com                       # what I get
dig @1.1.1.1 +short api.example.com              # what a public resolver gets
dig +trace api.example.com | tail -20            # follow the delegation from the root
dig NS example.com +short                        # who is authoritative
dig +norecurse @ns1.example.com api.example.com  # straight from the source, no cache
resolvectl status | head -20                     # what this host is configured to use
ResultMeaning
NXDOMAINThe name does not exist in the zone; check for a typo or a missing record
NOERROR with empty answerThe name exists but not with that type — often querying A when only CNAME or AAAA exists
SERVFAILResolver failed: broken delegation, unreachable authoritative server, or DNSSEC validation failure
REFUSEDThe server will not answer this query from you
Answer differs by resolverCaching, split-horizon DNS, or a stale record within TTL
Resolves but the app failsWrong record for the environment, or the app resolved once at startup

Reading dig output #

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23405
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0

;; ANSWER SECTION:
api.example.com.   60   IN   CNAME   lb.example.net.
lb.example.net.    30   IN   A       93.184.216.34

;; SERVER: 10.0.0.2#53(10.0.0.2)
;; Query time: 4 msec
FieldMeaning
statusResponse code; NOERROR here even with zero answers
flags: aaAuthoritative answer, straight from the zone
flags: raRecursion available at this server
TTL columnSeconds remaining, counting down inside a cache; a full TTL means a fresh fetch
SERVERWhich resolver actually answered — the first thing to check when results disagree
Query timeA slow first query and fast repeat means recursion, not a problem

dig +short is for scripts, plain dig is for diagnosis: the sections and flags are the diagnosis.

Record types #

TypePurposeNote
A / AAAAName to IPv4 / IPv6
CNAMEAlias to another nameCannot coexist with other records; never at a zone apex
ALIAS / ANAMEApex aliasProvider-specific, resolved server-side
MXMail exchangers with priorityLower number wins
TXTArbitrary textSPF, DKIM, domain verification
SRVService, port and target_service._proto.name
NSDelegation of a zoneMust match at parent and child
SOAZone metadata and serialSerial should increase on every change
PTRReverse mappingLives in in-addr.arpa / ip6.arpa
CAAWhich CAs may issue certificatesBlocks issuance by others

TTL and propagation #

There is no propagation — there is only cache expiry. A record is visible everywhere the moment it is published, except to resolvers still holding the previous answer, which expires after its TTL.

dig api.example.com | grep -E '^api'          # remaining TTL in a cache
watch -n5 'dig +short @8.8.8.8 api.example.com'

Lower the TTL to 60 seconds at least one old-TTL period before a planned change, then raise it afterwards. Negative answers are cached too, governed by the SOA minimum, so an NXDOMAIN fetched before the record existed can linger.

Search domains and ndots #

/etc/resolv.conf appends search domains to names with fewer than ndots dots. That is why api resolves inside a cluster and api.example.com. (trailing dot, fully qualified) skips the search list entirely.

nameserver 10.96.0.10
search myns.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

With ndots:5, api.example.com has three dots, so it is tried as api.example.com.myns.svc.cluster.local first, then the other search domains, and only then as written — four wasted queries per lookup. Fully qualify external names with a trailing dot in hot paths, or lower ndots per pod.

cat /etc/resolv.conf
resolvectl query api.example.com        # systemd-resolved: shows which link and cache
resolvectl flush-caches
resolvectl statistics | head

Kubernetes DNS #

CoreDNS serves cluster.local and forwards everything else upstream. Service names resolve to the ClusterIP; headless services resolve to pod IPs.

NameResolves to
apiSame namespace, via search domain
api.mynsService in another namespace
api.myns.svc.cluster.localFully qualified, no search expansion
10-1-2-3.myns.pod.cluster.localA pod address directly
_grpc._tcp.api.myns.svc.cluster.localSRV record with the named port
kubectl run -it --rm dnstest --image=nicolaka/netshoot -- dig api.myns.svc.cluster.local
kubectl -n kube-system logs -l k8s-app=kube-dns --tail 50
kubectl -n kube-system get cm coredns -o yaml
kubectl get endpointslice -n kube-system -l kubernetes.io/service-name=kube-dns

Intermittent resolution failures in a cluster are usually CoreDNS replicas being unhealthy, a NetworkPolicy dropping UDP 53, or conntrack pressure — not the records.

DNSSEC #

DNSSEC signs records so a resolver can prove an answer came from the zone owner and was not modified. It does not encrypt anything.

dig +dnssec api.example.com | grep -E 'RRSIG|ad;'    # ad flag = validated
dig +cd api.example.com                               # checking disabled: does it work unvalidated?
delv api.example.com                                  # full validation with reasoning

A SERVFAIL that becomes NOERROR with +cd is a validation failure: expired signatures or a DS record that does not match the zone’s keys.

Oneliners #

# Compare answers across public resolvers
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do printf '%s: %s\n' "$r" "$(dig +short @$r api.example.com | tr '\n' ' ')"; done

# All record types for a name
for t in A AAAA CNAME MX TXT NS SOA CAA; do printf '%-6s %s\n' "$t" "$(dig +short "$t" example.com | tr '\n' ' ')"; done

# TTL remaining, in seconds
dig api.example.com | awk '/^api/ {print $2; exit}'

# Every nameserver's view of a zone serial, to check replication
for ns in $(dig +short NS example.com); do printf '%s %s\n' "$ns" "$(dig +short SOA @"$ns" example.com | awk '{print $3}')"; done

# Reverse lookups for a subnet
for i in $(seq 1 20); do dig +short -x "10.0.0.$i" | sed "s/^/10.0.0.$i /"; done

# Resolve through a specific interface's resolver
resolvectl query --interface=eth0 api.example.com

# Which process is resolving what (systemd-resolved)
resolvectl monitor

# Verify SPF and DMARC exist
dig +short TXT example.com | grep -i spf; dig +short TXT _dmarc.example.com

# Check a CAA record before ordering a certificate
dig +short CAA example.com

# Measure resolution time from a pod
kubectl run -it --rm t --image=nicolaka/netshoot -- sh -c 'time dig +short api.example.com'

# Watch a record change during a cutover
while :; do printf '%s %s\n' "$(date +%T)" "$(dig +short @1.1.1.1 api.example.com)"; sleep 5; done

Further reading #

Last updated 15 September 2026 · Edit this page