systemd-resolved Explained: Why /etc/resolv.conf Is a Symlink Now

systemd-resolved Explained: Why /etc/resolv.conf Is a Symlink Now

On most modern distributions /etc/resolv.conf is a symlink and contains a nameserver you did not configure:

ls -l /etc/resolv.conf
# /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

cat /etc/resolv.conf
# nameserver 127.0.0.53
# options edns0 trust-ad
# search lan

127.0.0.53 is systemd-resolved, a local stub resolver. Applications query it, and it forwards to the real upstream servers.

This confuses people because the traditional way to change DNS, editing resolv.conf, now does nothing. The file is generated, and your edits are overwritten.

Why it exists

The old model had one global list of nameservers. That was fine when a machine had one network connection and breaks down when it has several.

Connect to a VPN and you need internal.company.com resolved by the company’s DNS server, and everything else by your normal resolver. With one global list you get whichever server is first, and either internal names fail or all your traffic leaks queries to the company resolver.

systemd-resolved keeps per-interface DNS configuration. Each link has its own servers and its own domains, and queries are routed by name.

It also adds caching, DNSSEC validation, DNS over TLS, and LLMNR and mDNS for local name resolution.

Seeing what is actually happening

resolvectl status
Global
       Protocols: LLMNR=resolve -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (enp3s0)
    Current Scopes: DNS LLMNR/IPv4
         Protocols: +DefaultRoute
Current DNS Server: 192.168.1.1
       DNS Servers: 192.168.1.1
        DNS Domain: lan

Link 5 (wg0)
    Current Scopes: DNS
         Protocols: -DefaultRoute
Current DNS Server: 10.8.0.1
       DNS Servers: 10.8.0.1
        DNS Domain: ~internal.company.com

This is the command that answers “where are my DNS queries actually going”, and it is the first thing to run on any DNS problem.

Two things in that output matter.

+DefaultRoute versus -DefaultRoute. A link with +DefaultRoute receives queries that do not match any specific domain. The WireGuard link has -DefaultRoute, so it only gets queries matching its domains.

The ~ prefix on internal.company.com. That is a routing domain: queries for that domain and its subdomains go to this link’s servers. Without the tilde it would be a search domain, meaning bare hostnames get it appended.

That distinction is the whole of split DNS and is worth internalising: ~domain routes, domain appends.

Querying

resolvectl query example.com
resolvectl query internal.company.com    # shows which link answered

resolvectl statistics                    # cache hit rate
resolvectl flush-caches                  # clear the cache

resolvectl query reports which interface and server handled the request, which diagnoses split DNS problems immediately.

Note that dig and nslookup talk to 127.0.0.53 and therefore go through resolved like everything else. To test an upstream server directly, bypass the stub:

dig @192.168.1.1 example.com
dig @1.1.1.1 example.com

Our dig guide covers reading the output.

Configuring it

# /etc/systemd/resolved.conf
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net
FallbackDNS=8.8.8.8
Domains=~.
DNSOverTLS=yes
DNSSEC=allow-downgrade
Cache=yes
sudo systemctl restart systemd-resolved
resolvectl status

Domains=~. routes all queries to these global servers, overriding what DHCP hands you. Without it, your router’s DNS usually wins because its link has +DefaultRoute.

#hostname after the address enables certificate validation for DNS over TLS. Without it, DNSOverTLS=yes encrypts but cannot verify the server’s identity.

DNSOverTLS=yes fails if encryption is unavailable. opportunistic falls back to plaintext, which means a network that blocks port 853 silently downgrades you.

Per-interface settings go through NetworkManager rather than here:

nmcli connection modify "Wired connection 1" ipv4.dns "1.1.1.1"
nmcli connection modify "Wired connection 1" ipv4.ignore-auto-dns yes
nmcli connection up "Wired connection 1"

Using it with Pi-hole

A common setup, with a common conflict. Pi-hole wants port 53, and on a machine running resolved, resolved may already have it.

sudo ss -tlnp | grep :53

resolved’s stub listens on 127.0.0.53:53 specifically, so it does not usually conflict with Pi-hole binding to the LAN address. If it does, disable the stub listener rather than the whole service:

[Resolve]
DNSStubListener=no

Then point /etc/resolv.conf at Pi-hole directly.

Disabling it entirely

Legitimate in some setups.

sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
sudo tee /etc/resolv.conf <<'EOF'
nameserver 1.1.1.1
nameserver 9.9.9.9
options edns0
EOF

This is not sufficient on its own. NetworkManager or your DHCP client will overwrite the file on the next connection. Tell NetworkManager to leave it alone:

# /etc/NetworkManager/conf.d/no-dns.conf
[main]
dns=none
sudo systemctl restart NetworkManager
sudo chattr +i /etc/resolv.conf    # belt and braces, makes it immutable

chattr +i prevents any process including root from modifying it. Remember you did this, because the next person to debug DNS on the machine will be confused. Remove with chattr -i.

Troubleshooting

Some domains resolve, others do not. Split DNS routing. resolvectl status and check which link has the domain, then resolvectl query the failing name.

DNS breaks after connecting to a VPN. The VPN link took +DefaultRoute and its server cannot resolve external names, or it did not and internal names fail. Adjust with resolvectl domain.

Changes to resolv.conf disappear. Expected. It is generated.

Intermittent failures with DNSSEC. Some networks and some domains have broken DNSSEC. DNSSEC=allow-downgrade is the pragmatic setting; DNSSEC=yes is stricter and will fail on misconfigured domains you may need to reach.

journalctl -u systemd-resolved -f

Our DNS explainer covers what resolution actually involves underneath.

Frequently Asked Questions

Why does /etc/resolv.conf contain 127.0.0.53?

That is systemd-resolved listening locally as a stub resolver. Applications send queries there, and resolved forwards them to the real upstream servers it learned from DHCP or your configuration. Editing resolv.conf directly does nothing useful because resolved manages the file and overwrites changes.

How do I see which DNS servers are actually in use?

Run resolvectl status, which shows the upstream servers per network interface along with the search domains and DNSSEC state. The contents of /etc/resolv.conf tell you only that queries go to the local stub, not where they go afterwards.

What is split DNS and why would I want it?

Split DNS routes queries for specific domains to specific servers, so internal.company.com resolves through the VPN while everything else uses your normal resolver. systemd-resolved does this per interface with routing domains, which is why it handles VPN connections better than a single global resolver list.

How do I disable systemd-resolved?

Stop and disable the systemd-resolved service, remove the /etc/resolv.conf symlink, and write a real file with your nameserver lines. Be aware that NetworkManager or your DHCP client may overwrite it, so you usually also need to tell them not to manage resolv.conf.

Why does DNS work for some domains but not others?

Usually a split DNS or routing domain issue, where queries for that domain are being sent to a server that cannot answer them. Run resolvectl query on the failing name to see which interface and server handled it, and resolvectl status to see the domain routing.

Does systemd-resolved support DNS over TLS?

Yes. Set DNSOverTLS to yes or opportunistic in /etc/systemd/resolved.conf, with yes requiring encryption and failing if unavailable. You also need DNS servers that support it, and specifying the server hostname alongside the address allows certificate validation.