Server

resolv.conf and systemd-resolved

By Prabath Thalangama· October 24, 2026· 3 min read
#linux#dns#systemd

Introduction

/etc/resolv.conf traditionally listed your nameservers. On current Ubuntu/Fedora/RHEL it's frequently a symlink to a file managed by systemd-resolved (or NetworkManager), and hand-edits get overwritten. You need to know which model your box uses.

The possibilities

Check what /etc/resolv.conf actually is:

ls -l /etc/resolv.conf
Target Manager Edit where
../run/systemd/resolve/stub-resolv.conf systemd-resolved (stub at 127.0.0.53) resolvectl / .network files / NM
../run/systemd/resolve/resolv.conf systemd-resolved (writes real upstreams directly) same
/run/NetworkManager/resolv.conf NetworkManager nmcli
a real file nobody — you own it edit directly

systemd-resolved

resolved runs a stub listener on 127.0.0.53:53. stub-resolv.conf contains just nameserver 127.0.0.53. Applications query the stub; resolved forwards to the real upstreams it learned from DHCP / config, applies per-link routing, caches, and does DNSSEC validation if enabled.

Set DNS:

# Per interface, at runtime
resolvectl dns eth0 1.1.1.1 9.9.9.9
resolvectl domain eth0 example.com '~corp.internal'   # ~ = routing-only domain

# Persistently via a .network file (systemd-networkd)
# /etc/systemd/network/10-eth0.network
[Match]
Name=eth0
[Network]
DNS=1.1.1.1
Domains=example.com ~corp.internal

# Or global fallback: /etc/systemd/resolved.conf
[Resolve]
DNS=1.1.1.1
FallbackDNS=9.9.9.9
DNSSEC=allow-downgrade

systemctl restart systemd-resolved after editing config files.

Split DNS

The killer feature: route different domains to different servers. A "routing-only" domain (~corp.internal) on the VPN interface means only *.corp.internal queries go to the corporate DNS; everything else uses your normal resolver. No more "the VPN broke all my DNS".

resolvectl domain tun0 '~corp.internal' '~10.in-addr.arpa'
resolvectl dns tun0 10.0.0.53

Debugging

resolvectl status                 # per-link DNS servers, domains, DNSSEC state
resolvectl query example.com      # resolve via resolved (shows which link/server answered)
resolvectl statistics             # cache hits, DNSSEC
resolvectl flush-caches
resolvectl reset-server-features  # after changing upstreams that misbehaved

# Bypass resolved entirely to test upstream directly
dig @1.1.1.1 example.com
dig @127.0.0.53 example.com        # via the stub

nslookup/dig with no @server may or may not go through the stub depending on the tool — dig reads /etc/resolv.conf (so the stub), resolvectl query is the definitive "what does the system do".

Verification and troubleshooting

  • Edited /etc/resolv.conf, reverted on reboot/reconnect — it's managed. Use resolvectl / NM / .network. To take manual control: rm /etc/resolv.conf; echo 'nameserver 1.1.1.1' > /etc/resolv.conf and disable the manager (or systemctl disable --now systemd-resolved and point the symlink elsewhere).
  • resolvectl status shows the right DNS but resolution fails — the app isn't using NSS//etc/resolv.conf (Go binaries with CGO_ENABLED=0 use their own resolver reading /etc/resolv.conf directly — fine — but musl/alpine containers differ), or nsswitch.conf hosts: line is odd.
  • VPN breaks all DNS — the VPN pushed a Domains=~. (route everything) or replaced the global DNS. Scope it: routing-only domains for the internal zones, keep the default resolver for the rest.
  • Slow first lookup, then fast — cache is working; the slow one is the upstream. resolvectl statistics for hit rate.
  • DNSSEC failures on a captive portal / corporate resolver — DNSSEC=allow-downgrade tolerates resolvers that strip DNSSEC; DNSSEC=no disables it.
  • 127.0.0.53 in a container — the stub isn't there; the container has its own /etc/resolv.conf. Don't copy the host's.
  • getent hosts x works, dig x fails (or vice versa) — getent uses NSS (includes /etc/hosts, mDNS); dig is pure DNS. The difference tells you where the name is defined.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.