Verify it's running
# Service status
systemctl status systemd-resolved
# What's in /etc/resolv.conf?
ls -la /etc/resolv.conf
# /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
cat /etc/resolv.conf
# nameserver 127.0.0.53 <-- the resolved stub
# options edns0 trust-ad
# search ...
# What does resolved actually do for queries?
resolvectl status
# Global settings + per-link DNS
If /etc/resolv.conf is not a symlink to ../run/systemd/resolve/stub-resolv.conf, resolved isn't the active resolver. NetworkManager / systemd-networkd / cloud-init might be writing a static file. Verify what's actually answering before debugging further.
Configure global upstreams + DoT
Edit /etc/systemd/resolved.conf:
[Resolve]
# Upstream DNS servers (fall-through if per-link DNS doesn't provide)
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net
# Fallback if the above are unreachable
FallbackDNS=8.8.8.8
# DNS-over-TLS to the configured DNS servers
DNSOverTLS=yes
# DNSSEC validation
DNSSEC=yes
# Cache
Cache=yes
CacheFromLocalhost=no
# mDNS for *.local resolution (handled by Avahi if installed; see /tutorials/mdns-avahi-zero-config-naming.html)
MulticastDNS=resolve
LLMNR=resolve
# Reject queries for domains in private IP space coming from public sources
DNSStubListenerExtra=
sudo systemctl restart systemd-resolved
resolvectl status
Now every DNS query the local machine makes goes through TLS to Cloudflare / Quad9; resolved caches answers in-process; DNSSEC validation is on.
Per-link DNS
The killer feature: each network interface can have its own DNS upstream + search domain. When the corporate VPN comes up, resolved learns the VPN's pushed DNS servers + search domain (e.g. internal.example.com); queries for *.internal.example.com go to the VPN's resolver while everything else uses the global upstream.
resolvectl status
# Look for per-interface sections:
# Link 3 (tun0)
# Current Scopes: DNS LLMNR/IPv4
# Protocols: +DefaultRoute -LLMNR -mDNS DNSOverTLS=opportunistic DNSSEC=no/unsupported
# Current DNS Server: 10.0.5.1
# DNS Servers: 10.0.5.1
# DNS Domain: ~internal.example.com
The ~ prefix means "this DNS server handles only queries for this domain." Without ~ (just DNS Domain: internal.example.com), it's a search suffix.
To set per-link DNS manually:
sudo resolvectl dns tun0 10.0.5.1
sudo resolvectl domain tun0 '~internal.example.com'
NetworkManager normally manages this automatically; manual setting is for testing.
Query individual zones / show DNS server selection
# Resolve a name
resolvectl query example.com
# example.com: 93.184.216.34
# -- link: eth0
# -- protocol: dns
# -- server: 1.1.1.1
# Force a specific interface
resolvectl query -i tun0 internal.example.com
# DNSSEC validation status
resolvectl query --validate=yes example.com
# Reverse
resolvectl query 93.184.216.34
The output shows which interface the name was resolved through — useful for "why is this internal name failing? Which DNS server did it actually go to?"
Inspect cache + statistics
resolvectl statistics
# Transactions
# Current Transactions: 0
# Total Transactions: 12345
# Cache
# Current Cache Size: 142
# Cache Hits: 5678
# Cache Misses: 89
# DNSSEC Verdicts
# Secure: 0
# Insecure: 5678
# Bogus: 0
# Indeterminate: 0
# Flush the cache (after DNS changes)
sudo resolvectl flush-caches
Cache hit rate is the easiest indicator of resolver performance; on a desktop, expect 70-90% after warm-up.
When resolved is the wrong tool
- Pi-hole / Unbound on the LAN. If you're running a recursive resolver on the network (see that tutorial), point resolved at it as the upstream — or, if you don't want resolved's per-link logic, disable it and write
/etc/resolv.confdirectly. - Containers. systemd-resolved doesn't run inside most containers; container runtimes have their own resolvers (Docker uses an embedded DNS forwarder; Podman defaults to whatever's in the host's resolv.conf).
- nsswitch.conf order matters. If you've configured Avahi for mDNS (see that tutorial) and resolved is also handling mDNS, the two can race. Pick one for
.localresolution; disable mDNS in the other.
Disable it cleanly
To go back to old-school /etc/resolv.conf:
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
# Write a plain file
echo -e "nameserver 1.1.1.1\nnameserver 9.9.9.9" | sudo tee /etc/resolv.conf
# Make sure NetworkManager doesn't overwrite it
# In /etc/NetworkManager/NetworkManager.conf:
[main]
dns=none
For a server that only needs upstream-DNS-from-a-static-file, this is fine. For laptops crossing networks (corporate VPN + home + coffee shop), resolved's per-link awareness is worth the complexity.
Worth knowing
- NSS configuration.
/etc/nsswitch.confhas ahosts:line that picks the resolution order:files dnsmeans/etc/hostsfirst then DNS. With resolved, the order is usuallyfiles mymachines resolve [!UNAVAIL=return] dns;resolvecalls into resolved over D-Bus before falling back to plain DNS. - D-Bus interface. Applications can talk to resolved via D-Bus, getting richer answers than the simple
getaddrinfopath — multi-record returns with TTLs, DNSSEC results, per-link source identification. resolvectl monitor— live stream of resolution events for debugging "what's resolving right now and where to?"