When systemd‑resolved ignores /etc/hosts entries for local subdomains

Why /etc/hosts still matters

Even with systemd‑resolved becoming the default in most modern distros, that old /etc/hosts file is still the fastest way to pin a name to an IP on a single box or a tiny LAN. I’ve seen people drop the file entirely, thinking DNS is always the answer, and then run into a maze of “host not found” errors. The culprit? systemd‑resolved quietly skipping some /etc/hosts entries—especially the ones that look like subdomains of a local domain.

The default resolution order in systemd‑resolved

Systemd‑resolved hooks into the Name Service Switch (NSS). When a program asks for a hostname, NSS consults the modules listed in /etc/nsswitch.conf. The default line for host resolution is:

hosts: files resolve [!UNAVAIL=return] dns
  • files – reads /etc/hosts
  • resolve – talks to systemd‑resolved
  • dns – falls back to the configured DNS servers

The [!UNAVAIL=return] flag tells NSS to bail out if the resolve module can’t answer (e.g., no network). Because resolve comes before dns, a good /etc/hosts line should win. The hiccup happens when the resolve module decides a name isn’t “local” and skips the files lookup for that particular query.

When systemd‑resolved ignores /etc/hosts entries

Subdomains vs. top‑level hostnames

Systemd‑resolved treats names ending in a dot (.) or matching the local domain as local. The local domain is usually derived from the system’s hostname or from the Domains= setting in /etc/systemd/resolved.conf. For example, on a box named server1.example.com, the local domain is example.com. If you add this to /etc/hosts:

192.168.1.10  app.example.com

systemd‑resolved will see app.example.com as a subdomain of the local domain and won’t consult /etc/hosts. Instead, it will fire a DNS query for app.example.com to the upstream servers. If those servers don’t have the record, the lookup fails.

A top‑level name like app or a fully‑qualified name that does not match the local domain (e.g., app.local) will resolve via /etc/hosts as expected.

When you type a bare hostname (app) in a browser or a command, systemd‑resolved appends the search suffixes defined in /etc/resolv.conf (or via Domains=). If the search suffix is example.com, the lookup becomes app.example.com. The same rule applies to /etc/hosts: if the name you query ends up matching the local domain, systemd‑resolved bypasses the files module.

DNSSEC and secure DNS

If systemd‑resolved is configured to use DNSSEC validation (DNSSEC=yes in resolved.conf), it will reject any DNS response that fails validation. In that case, even if a DNS server returns a record for app.example.com, the lookup will fail, and systemd‑resolved will not fall back to /etc/hosts. That can give the impression that /etc/hosts is ignored, when in fact the DNS query is simply rejected.

Practical troubleshooting steps

Verify /etc/hosts syntax

The simplest cause of a silent ignore is a syntax error. Each line must contain an IP address followed by one or more hostnames, separated by whitespace. Trailing comments are allowed but must start with #. Example:

192.168.1.10  app.example.com   # local app

Run:

sudo systemd-resolve --flush-caches

to clear any cached results before testing.

Check systemd‑resolved config

Open /etc/systemd/resolved.conf and look for the Domains= line. If it is set to ~ (tilde), systemd‑resolved will use the system’s hostname as the local domain. If it is set to a specific domain, that domain becomes the local domain. Example:

[Resolve]
Domains=example.com

If you want all subdomains of example.com to be treated as local, you can add a trailing dot:

Domains=example.com.

A trailing dot forces systemd‑resolved to treat the domain as fully qualified and not apply search suffixes.

Use systemd-resolve --status

This command shows the current resolution state, including the local domain, DNS servers, and whether DNSSEC is enabled:

systemd-resolve --status

Look for the “DNS Servers” and “DNSSEC” sections. If DNSSEC is yes, consider disabling it temporarily to see if the issue persists.

Use dig +trace

dig can show the exact path the query takes. For a name that should resolve via /etc/hosts, run:

dig +trace app.example.com

If the output shows a query to your upstream DNS server, systemd‑resolved is not using /etc/hosts. If it shows ;; SERVER: 127.0.0.53#53, it is using the local stub resolver.

Workarounds and best practices

Use systemd‑resolved’s local domain feature

If you control a local domain, add it to Domains= in resolved.conf. This tells systemd‑resolved that any name ending with that domain is local. It will then consult /etc/hosts for those names. Example:

[Resolve]
Domains=example.com

After editing, reload:

sudo systemctl restart systemd-resolved

Use fully‑qualified names in /etc/hosts

Avoid relying on search suffixes. Write the full domain name in /etc/hosts:

192.168.1.10  app.example.com

and query it with the dot:

ping app.example.com.

The trailing dot forces a fully‑qualified lookup and bypasses search suffixes.

Use DNS over TLS/HTTPS for internal DNS

If you run a local DNS server (e.g., dnsmasq, bind9, or unbound), consider configuring systemd‑resolved to use DNS‑over‑TLS or DNS‑over‑HTTPS. That keeps the internal DNS traffic encrypted and can help avoid confusion between local and external names.


TAGS: #linux #dns #systemd #security


See also