Read DNS Answers
Problem statement
Read saved dig output and pull out the answer: the status, the chain of names, the IP addresses and how long each answer may be cached. Then check whether the server's /etc/hosts file overrides DNS for that name. "It's always DNS" is a running joke because so many outages start there, and reading dig output is how you check.
dig.txt (from dig api.example.com)
; <<>> DiG 9.18.28 <<>> api.example.com;; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4242;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION:;api.example.com. IN A ;; ANSWER SECTION:api.example.com. 300 IN CNAME lb.example.net.lb.example.net. 60 IN A 203.0.113.10lb.example.net. 60 IN A 203.0.113.11 ;; Query time: 12 msec;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)hosts (a copy of the server's /etc/hosts)
127.0.0.1 localhost10.0.0.50 api.example.com- Print the status of the answer.
- Print each record in the answer section: name, type, value and TTL.
- Print just the IP addresses, as
dig +shortwould show at the end. - Say whether
/etc/hostsoverrides the name, and which address the server would really use.
Expected output:
== status ==status: NOERROR== answer records ==api.example.com. CNAME lb.example.net. ttl 300slb.example.net. A 203.0.113.10 ttl 60slb.example.net. A 203.0.113.11 ttl 60s== just the addresses (like dig +short) ==203.0.113.10203.0.113.11== does /etc/hosts override it? ==yes: hosts file says 10.0.0.50, and it is checked before DNSHints
status:. The answer records are the lines after ;; ANSWER SECTION: up to the next empty line.Approach
Optimal: dig answer section with awk
Covers: DNS records (A, AAAA, CNAME), TTL, dig output sections, status: (NOERROR, NXDOMAIN, SERVFAIL), dig +short, /etc/hosts and the lookup order, getent hosts, awk flags for "inside a section".
What a DNS lookup returns. You ask for a name, and DNS answers with records:
lb.example.net"]:::purple C --> A1["A
203.0.113.10"]:::green C --> A2["A
203.0.113.11"]:::green classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px
| Type | Means | Example |
|---|---|---|
A |
an IPv4 address | 203.0.113.10 |
AAAA |
an IPv6 address | 2001:db8::10 |
CNAME |
"this name is another name", follow it | lb.example.net. |
MX |
mail server | |
TXT |
text, often for verification |
Here api.example.com is a CNAME for lb.example.net, which has two A records. Clients pick one, which is a simple form of load balancing.
TTL is how long to cache. The number after the name is the TTL (time to live) in seconds. Resolvers keep the answer that long before asking again. A 60-second TTL means a change takes up to a minute to reach everyone; a TTL of a day means a mistake lasts a day. Lower the TTL before a planned move.
The status line.
| Status | Means |
|---|---|
NOERROR |
the name exists (the answer may still be empty for this type) |
NXDOMAIN |
the name does not exist |
SERVFAIL |
the DNS server failed to answer, often a broken zone or DNSSEC problem |
REFUSED |
the server will not answer you |
/etc/hosts can win. On most Linux systems, /etc/nsswitch.conf says hosts: files dns, which means the hosts file is checked first. An old line in /etc/hosts silently beats DNS, so dig (which asks DNS directly) shows one address while the app connects to another. getent hosts api.example.com shows what the system really uses.
10.0.0.50"]:::red H --> D(["ask DNS"]):::blue D --> DA["203.0.113.10 or .11"]:::green classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px
Reading a section with awk. The answer lines come after a marker and end at an empty line. A flag handles that: /^;; ANSWER SECTION:/ { f = 1; next } turns it on, /^$/ { f = 0 } turns it off, and the pattern f means "only while the flag is on".
Walking through the code. The # Setup: lines only save the two sample files, so skip past them.
grep -o 'status: [A-Z]*'pulls the status out of the header.- The awk prints name, type, value and TTL for each answer line.
- The same flag with
$4 == "A"prints only the addresses. - A second awk looks for the name in the hosts file and reports the override.
Edge cases. Names in dig end with a dot, the root of DNS; strip it if you compare names. dig is in the dnsutils or bind-utils package and may be missing; nslookup, host and getent hosts are alternatives.
# Setup: save sample dig output and a hosts file in a fresh temporary folder
cd "$(mktemp -d)"
cat > dig.txt << 'OUT'
; <<>> DiG 9.18.28 <<>> api.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4242
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; QUESTION SECTION:
;api.example.com. IN A
;; ANSWER SECTION:
api.example.com. 300 IN CNAME lb.example.net.
lb.example.net. 60 IN A 203.0.113.10
lb.example.net. 60 IN A 203.0.113.11
;; Query time: 12 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
OUT
printf '127.0.0.1 localhost\n10.0.0.50 api.example.com\n' > hosts
name=api.example.com
echo "== status =="
grep -o 'status: [A-Z]*' dig.txt
echo "== answer records =="
awk '/^;; ANSWER SECTION:/ { f = 1; next } /^$/ { f = 0 }
f { printf "%-17s %-6s %-16s ttl %ss\n", $1, $4, $5, $2 }' dig.txt
echo "== just the addresses (like dig +short) =="
awk '/^;; ANSWER SECTION:/ { f = 1; next } /^$/ { f = 0 } f && $4 == "A" { print $5 }' dig.txt
echo "== does /etc/hosts override it? =="
awk -v n="$name" '$2 == n { print "yes: hosts file says " $1 ", and it is checked before DNS"; found = 1 }
END { if (!found) print "no: DNS answer is used" }' hostsInterview follow-ups
Check that every name in a list resolves, and print the ones that do not.
Loop over the names and use
getent hosts, which follows the same order the system uses:while read -r n; do getent hosts "$n" > /dev/null || echo "FAILED: $n"; done < names.txt.getentexits non-zero when the name cannot be resolved. For checking DNS itself, ignoring the hosts file, usedig +short "$n"and treat empty output as a failure.
Frequently asked questions
Three usual causes. The hosts file has an old entry, which getent hosts name reveals. A cache somewhere still holds the old answer until its TTL runs out: the local resolver (resolvectl flush-caches on systemd-resolved), the app itself (Java caches DNS by default), or a proxy. Or dig is asking a different DNS server than the system uses; dig shows the one it asked on the SERVER: line, and /etc/resolv.conf shows what the system uses.
dig @8.8.8.8 api.example.com asks Google's public resolver instead of your default, which is the quickest way to tell "broken everywhere" from "broken on my resolver". dig +trace api.example.com starts at the root servers and follows the delegation down, showing where an answer goes wrong. dig api.example.com AAAA or MX asks for other record types. +short prints only the values.
NXDOMAIN means the authoritative servers say the name does not exist: a typo, a deleted record, or a zone that was not delegated. SERVFAIL means the resolver could not get a good answer at all: the authoritative servers are down or unreachable, the zone is broken, or DNSSEC checks failed. With SERVFAIL, try dig +trace and another resolver to find which step fails.