Networking
DNS
Resolving names with dig, reading the answer, TTL and caching behaviour, search domains and Kubernetes resolution.
Cheatsheet #
| Task | Command |
|---|---|
| Resolve a name | dig +short api.example.com |
| Which resolver answered | dig api.example.com | grep SERVER |
| Bypass the local cache | dig @1.1.1.1 api.example.com |
| Ask the authoritative server | dig +norecurse @ns1.example.com api.example.com |
| Full delegation path | dig +trace api.example.com |
| Reverse lookup | dig -x 93.184.216.34 +short |
| Nameservers for a zone | dig NS example.com +short |
| Mail records | dig MX example.com +short |
| TXT (SPF, verification) | dig TXT example.com +short |
| Zone serial | dig SOA example.com +short |
| Does the resolver cache it | dig api.example.com | grep -A1 'ANSWER SECTION' (TTL counts down) |
| What the system resolver does | resolvectl query api.example.com |
| In-cluster name | kubectl 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| Result | Meaning |
|---|---|
NXDOMAIN | The name does not exist in the zone; check for a typo or a missing record |
NOERROR with empty answer | The name exists but not with that type — often querying A when only CNAME or AAAA exists |
SERVFAIL | Resolver failed: broken delegation, unreachable authoritative server, or DNSSEC validation failure |
REFUSED | The server will not answer this query from you |
| Answer differs by resolver | Caching, split-horizon DNS, or a stale record within TTL |
| Resolves but the app fails | Wrong 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| Field | Meaning |
|---|---|
status | Response code; NOERROR here even with zero answers |
flags: aa | Authoritative answer, straight from the zone |
flags: ra | Recursion available at this server |
| TTL column | Seconds remaining, counting down inside a cache; a full TTL means a fresh fetch |
SERVER | Which resolver actually answered — the first thing to check when results disagree |
Query time | A 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 #
| Type | Purpose | Note |
|---|---|---|
A / AAAA | Name to IPv4 / IPv6 | |
CNAME | Alias to another name | Cannot coexist with other records; never at a zone apex |
ALIAS / ANAME | Apex alias | Provider-specific, resolved server-side |
MX | Mail exchangers with priority | Lower number wins |
TXT | Arbitrary text | SPF, DKIM, domain verification |
SRV | Service, port and target | _service._proto.name |
NS | Delegation of a zone | Must match at parent and child |
SOA | Zone metadata and serial | Serial should increase on every change |
PTR | Reverse mapping | Lives in in-addr.arpa / ip6.arpa |
CAA | Which CAs may issue certificates | Blocks 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:5With 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 | headKubernetes DNS #
CoreDNS serves cluster.local and forwards everything else upstream. Service names resolve to the ClusterIP; headless services resolve to pod IPs.
| Name | Resolves to |
|---|---|
api | Same namespace, via search domain |
api.myns | Service in another namespace |
api.myns.svc.cluster.local | Fully qualified, no search expansion |
10-1-2-3.myns.pod.cluster.local | A pod address directly |
_grpc._tcp.api.myns.svc.cluster.local | SRV 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-dnsIntermittent 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 reasoningA 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; doneFurther reading #
man dig, and RFC 1034 for the model- Kubernetes DNS specification