DNS and name resolution

hosts, resolved, dig and getent.

Beginner14 min · lesson 24 of 29

Programs connect to names such as api.example.com, and before any connection starts the system has to turn the name into an address. This lesson follows that lookup on Ubuntu 26.04 and RHEL 10: which sources are consulted and in what order, the local resolver Ubuntu runs, why getent and dig can disagree, the record types you will meet, and what each error message says about which part failed. Many reports that "the network is down" turn out to be name resolution, and the error text usually tells you so.

Where a name is looked up

Programs do not usually speak DNS themselves. They call the C library, which reads the hosts line of /etc/nsswitch.conf to learn which sources to consult, in order:

deploy@web01 · Ubuntu 26.04 LTS
$ grep ^hosts /etc/nsswitch.conf
hosts: files dns
deploy@rocky10 · Rocky Linux 10.2
$ grep ^hosts /etc/nsswitch.conf
hosts: files dns myhostname

files is the file /etc/hosts and dns means the DNS servers named in /etc/resolv.conf. The first source that knows the name wins. RHEL adds myhostname, which always resolves the machine's own name and localhost even when the other sources fail. /etc/hosts is a plain list of addresses and names:

deploy@web01 · Ubuntu 26.04 LTS
$ cat /etc/hosts
127.0.0.1 localhost # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 192.168.5.2 host.lima.internal 127.0.1.1 web01

The first lines map localhost and the IPv6 loopback names. 127.0.1.1 web01 is the Debian and Ubuntu convention for the machine's own name, written here by cloud-init. The host.lima.internal line was added by the lab's virtualisation tool. getent hosts asks the same question a program asks, through the same sources in the same order, so it shows what programs will get:

deploy@web01 · Ubuntu 26.04 LTS
$ getent hosts web01
127.0.1.1 web01

The local resolver on Ubuntu

On Ubuntu, programs send their DNS queries to systemd-resolved, a local service that forwards them to the real DNS servers and caches the answers. /etc/resolv.conf points at it:

deploy@web01 · Ubuntu 26.04 LTS
$ ls -l /etc/resolv.conf grep -v "^#" /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Sep 18 16:31 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf nameserver 127.0.0.53 options edns0 trust-ad search .

The file is a link to one that systemd-resolved maintains, and the only server it names is 127.0.0.53, the resolver's stub listener on the loopback interface. The real servers are shown by resolvectl status:

deploy@web01 · Ubuntu 26.04 LTS
$ resolvectl status
Global Protocols: -LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub Link 2 (eth0) Current Scopes: DNS mDNS/IPv4 mDNS/IPv6 Protocols: +DefaultRoute -LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 192.168.5.2 DNS Servers: 192.168.5.2 Default Route: yes

Current DNS Server is the server queries go to, learned from DHCP for eth0. In this lab it is the virtualisation tool's gateway, which passes queries on to the workstation's own resolver. +mDNS (multicast DNS for names on the local network) is switched on by the virtualisation tool; Ubuntu ships it off. The resolver also listens on 127.0.0.54, a plain proxy that passes queries to the same servers without adding its own features. Ubuntu configures systemd-resolved with Cache=no-negative (in /usr/lib/systemd/resolved.conf.d/cache-no-negative.conf), so it caches answers that exist but not "no such name" replies.

RHEL does not run systemd-resolved. NetworkManager writes the DNS server it received straight into /etc/resolv.conf, and programs query that server directly:

deploy@rocky10 · Rocky Linux 10.2
$ ls -l /etc/resolv.conf cat /etc/resolv.conf
-rw-r--r--. 1 root root 53 Sep 26 19:33 /etc/resolv.conf # Generated by NetworkManager nameserver 192.168.5.2
$ systemctl is-active systemd-resolved NetworkManager
inactive active

getent and dig ask different questions

getent hosts answers "what address will a program get for this name?". dig, from the bind9-dnsutils package that Ubuntu Server installs by default (bind-utils on RHEL), answers a narrower question: "what does this DNS server say?". It sends one DNS query and prints the whole reply:

deploy@web01 · Ubuntu 26.04 LTS
$ getent hosts example.com
172.66.147.243 example.com 104.20.23.154 example.com
$ dig example.com
; <<>> DiG 9.20.24-1ubuntu0.3-Ubuntu <<>> example.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30439 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;example.com. IN A ;; ANSWER SECTION: example.com. 0 IN A 172.66.147.243 example.com. 0 IN A 104.20.23.154 ;; Query time: 1 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) ;; WHEN: Sun Sep 27 08:20:57 UTC 2026 ;; MSG SIZE rcvd: 72

Read the reply from the top. status: NOERROR means the server answered normally. The QUESTION SECTION repeats what was asked: the name, class IN (internet) and type A (an IPv4 address, the default). Each line of the ANSWER SECTION is a record: name, TTL, class, type and data. The TTL, time to live, is how many seconds a resolver may cache the answer; this lab's upstream server always reports 0, which real resolvers do not. SERVER shows who answered, the local stub. dig +short prints only the data.

The difference matters when /etc/hosts is involved. Add a name that exists only in that file:

deploy@web01 · Ubuntu 26.04 LTS
$ echo "192.0.2.40 intranet.example" | sudo tee -a /etc/hosts
192.0.2.40 intranet.example
$ getent hosts intranet.example
192.0.2.40 intranet.example
$ resolvectl query intranet.example
intranet.example: 192.0.2.40 -- Information acquired via protocol DNS in 2.5ms. -- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes -- Data from: synthetic
$ dig +short intranet.example
192.0.2.40
$ dig @9.9.9.9 intranet.example
… ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 53240 … ;; SERVER: 9.9.9.9#53(9.9.9.9) (UDP) …

getent finds the name in /etc/hosts, as every program on the machine will. dig itself never reads that file, yet here it gets the answer too: systemd-resolved also reads /etc/hosts and answers from it, which resolvectl query marks as Data from: synthetic. It also reports such local data as authenticated, because resolved trusts what it reads from its own machine; no DNSSEC check was involved. Only when dig asks a real DNS server directly, here the public resolver Quad9 at 9.9.9.9 with @9.9.9.9, does the truth appear: NXDOMAIN, no such name. On RHEL, which has no systemd-resolved, a plain dig already asks the real server. So to learn what DNS itself says, name the server: dig @SERVER NAME. Remove the test line again; systemd-resolved notices the change within a few seconds:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo sed -i "/intranet.example/d" /etc/hosts
$ getent hosts intranet.example

Record types

A DNS name can hold records of several types. Because this lab's upstream server reports every TTL as 0, this section asks Quad9 directly, and +noall +answer prints just the answer lines:

deploy@web01 · Ubuntu 26.04 LTS
$ dig +noall +answer @9.9.9.9 example.com A dig +noall +answer @9.9.9.9 example.com AAAA dig +noall +answer @9.9.9.9 example.com MX dig +noall +answer @9.9.9.9 example.com TXT
example.com. 300 IN A 104.20.23.154 example.com. 300 IN A 172.66.147.243 example.com. 204 IN AAAA 2606:4700:10::6814:179a example.com. 204 IN AAAA 2606:4700:10::ac42:93f3 example.com. 300 IN MX 0 . example.com. 300 IN TXT "v=spf1 -all" example.com. 300 IN TXT "_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9"
$ dig +noall +answer @9.9.9.9 www.iana.org
www.iana.org. 3600 IN CNAME www.iana.org.cdn.cloudflare.net. www.iana.org.cdn.cloudflare.net. 300 IN A 104.18.24.232 www.iana.org.cdn.cloudflare.net. 300 IN A 104.18.25.232
$ dig +noall +answer @9.9.9.9 example.com NS
example.com. 15270 IN NS elliott.ns.cloudflare.com. example.com. 15270 IN NS hera.ns.cloudflare.com.
$ dig +noall +answer @9.9.9.9 -x 9.9.9.9
9.9.9.9.in-addr.arpa. 35667 IN PTR dns9.quad9.net.

A records hold IPv4 addresses and AAAA records IPv6 addresses; a name can have several of each. MX names the mail server for a domain; 0 . is a "null MX", which says the domain accepts no mail. TXT holds free text, used for things such as SPF mail policies (v=spf1 -all says no server may send mail for this domain) and ownership checks. CNAME makes a name an alias: www.iana.org points to a name at its content delivery network, and the resolver follows it to the A records. NS lists the servers that are authoritative for a domain, the ones that hold its records, and PTR maps an address back to a name, which dig -x asks for.

The TTL is why a DNS change takes time to reach everyone: a resolver that cached the old answer keeps using it until its TTL runs out. sudo resolvectl flush-caches empties the cache on this machine only; other resolvers keep theirs.

When a name does not resolve

Names under .invalid are reserved and never exist, which makes them safe for producing the "no such name" error. Each tool words it differently:

deploy@web01 · Ubuntu 26.04 LTS
$ getent hosts nosuch.invalid
$ ping -c 1 nosuch.invalid
ping: nosuch.invalid: Name or service not known
$ curl -sS http://nosuch.invalid/
curl: (6) Could not resolve host: nosuch.invalid
$ dig nosuch.invalid
… ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 39112 … ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) …

getent prints nothing and exits with status 2. Name or service not known is the C library's message for a name that does not exist, and ssh and many other programs print the same text. In DNS terms the answer was NXDOMAIN, "no such domain". A different answer, SERVFAIL, means the resolver could not get a trustworthy answer at all. The domain below has deliberately broken DNSSEC signatures (DNSSEC signs DNS records so resolvers can check them), and Quad9, which checks them, refuses to answer:

deploy@web01 · Ubuntu 26.04 LTS
$ dig @9.9.9.9 dnssec-failed.org
… ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 48636 … ; EDE: 7 (Signature Expired) … ;; SERVER: 9.9.9.9#53(9.9.9.9) (UDP) …

EDE, an extended DNS error, gives the reason. In daily work SERVFAIL usually means the domain's own servers are down or misconfigured, which you cannot fix from your side.

The third failure is a resolver that does not answer at all. To produce it safely, the lab uses a network namespace, an isolated copy of the network stack in which nothing listens on 127.0.0.53, as if systemd-resolved had stopped. Commands prefixed with sudo ip netns exec ess-dns-off run inside it:

deploy@web01 · Ubuntu 26.04 LTS
$ sudo ip netns add ess-dns-off sudo ip -n ess-dns-off link set lo up
$ sudo ip netns exec ess-dns-off getent hosts web01
127.0.1.1 web01
$ sudo ip netns exec ess-dns-off ping -c 1 example.com
ping: example.com: Temporary failure in name resolution
$ sudo ip netns exec ess-dns-off curl -sS http://example.com/
curl: (6) Could not resolve host: example.com
$ sudo ip netns exec ess-dns-off dig example.com
;; communications error to 127.0.0.53#53: connection refused ;; communications error to 127.0.0.53#53: connection refused ;; communications error to 127.0.0.53#53: connection refused ; <<>> DiG 9.20.24-1ubuntu0.3-Ubuntu <<>> example.com ;; global options: +cmd ;; no servers could be reached
$ sudo ip netns del ess-dns-off

Names from /etc/hosts still resolve, because files comes first and needs no network. Everything else fails with Temporary failure in name resolution, which means no DNS server answered: the name may be fine. dig shows why, with connection refused on 127.0.0.53. curl prints the same message for both failures, so check with getent or dig before deciding which one you have. On a real Ubuntu server, resolvectl status and systemctl status systemd-resolved are the next commands.

Which part of name resolution failed?
A program cannot look up a name
compare getent hosts NAME with dig @SERVER NAME
Name or service not known
The name does not exist
spelling, the domain, dig shows NXDOMAIN
Temporary failure in name resolution
No DNS server answered
resolvectl status, the network, port 53
dig status: SERVFAIL
The resolver could not get an answer
the domain's own servers or DNSSEC
getent and dig disagree
Another source answers first
look for the name in /etc/hosts

Because files comes first, a line in /etc/hosts silently overrides DNS for every program on the machine, which is why attackers with root access sometimes add entries there, for example to redirect an update or licence server. Whoever controls the resolver can redirect every lookup, so check that resolvectl status (or /etc/resolv.conf on RHEL) names the servers you expect. DNS can also carry data out of a network, encoded in the names being looked up, so a flood of lookups for odd names under one unfamiliar domain is worth investigating.

Try this

Point a name that exists in DNS somewhere else and watch who notices: add 192.0.2.50 example.com to /etc/hosts with echo "192.0.2.50 example.com" | sudo tee -a /etc/hosts. Wait a few seconds, then predict and check each result: getent hosts example.com and dig +short example.com both return 192.0.2.50 (the second through systemd-resolved), dig +short @9.9.9.9 example.com returns the real addresses, and the first line of ping -c 1 -W 2 example.com shows that ping uses 192.0.2.50. Remove the line with sudo sed -i "/192.0.2.50 example.com/d" /etc/hosts, wait a few seconds, and check that getent returns the real addresses again.

Takeaway

When a name will not resolve, compare getent hosts NAME (what programs get) with dig @SERVER NAME (what DNS says), then read the message: Name or service not known means the name does not exist, Temporary failure in name resolution means no DNS server answered.

Quick check
01On an application server, getent hosts db01.corp.example prints 192.0.2.20, but dig @198.51.100.53 db01.corp.example, asked of the company's DNS server, returns NXDOMAIN. What explains it?
Incorrect — The DNS server answered, with NXDOMAIN, so it is up. getent keeps no cache of its own.
Incorrect — The trailing dot only marks a name as complete; dig asks for the full name either way.
Incorrect — getent follows the hosts line of nsswitch.conf, which includes dns after files.
Correct — files comes before dns in nsswitch.conf, and dig only sends DNS queries to a server.
02An application logs "Temporary failure in name resolution" for every external name, yet getent hosts web01 still prints the address from /etc/hosts. What has failed?
Incorrect — A name that does not exist gives "Name or service not known", not a temporary failure.
Correct — /etc/hosts needs no network and still works; every lookup that needs a DNS server fails.
Incorrect — The order changes which source answers first. It cannot make every DNS lookup fail.
Incorrect — resolv.conf is readable by everyone, and a permission error would be reported as one.
03You moved www.example.org to a new address an hour ago. Your laptop gets the new address, but a colleague still gets the old one, and their resolver returns the old record with a TTL of 70000. Why?
Incorrect — Registrars are not involved in changing a record. The authoritative servers already give the new address, as your laptop shows.
Incorrect — Flushing clears only your own machine's cache. Their resolver keeps its own copy.
Correct — A resolver may reuse an answer for its TTL, here about 19 more hours, unless it is flushed.
Incorrect — The record their resolver returns is the old one, whatever the address family.

Related