Editing resolv.conf Does Not Fix DNS After the VPN Disconnects
A dead tunnel resolver at the top of the list costs a timeout per lookup. Check who owns the file, reset the link state, and stop editing generated output.
You disconnect the VPN, ping 1.1.1.1 still answers, and every name lookup hangs before the page finally gives up. Then cat /etc/resolv.conf shows a nameserver that only ever existed inside the tunnel, usually first in the list. Deleting that line appears to fix it for about ten minutes, until a suspend, a Wi-Fi roam or a restart puts it back. The file you edited is not the setting; it is a rendering of link state that another component owns.
Why one stale entry costs seconds per lookup
The glibc resolver walks the nameserver list in order and waits out a timeout before trying the next one, and resolv.conf(5) caps that list at three entries. Put a tunnel-side resolver such as 10.8.0.1 at the top after the tunnel is gone and every lookup pays the timeout before falling through to a server that answers. That is why the symptom looks like a dead connection rather than a DNS error: browsers block, time out, and sometimes serve a cached address that later goes stale.
It also explains why the complaint arrives as "the internet is broken". A ping to an IP address proves the link is fine, which makes the fault look like the application. The resolver list is the first thing to read and the last thing to edit.
The myth: resolv.conf is a settings file
Ask what the path actually is before you change it:
ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
On a systemd-resolved host it is a symlink to a stub file pointing at 127.0.0.53, and the real servers live in per-link state that resolvectl prints. On a NetworkManager host it may be a file NetworkManager writes from the active connection. With some vendor clients it is a plain file rewritten in place on connect and, if you are unlucky, never restored on disconnect. Those are three different owners, and editing the first two by hand either does nothing or lasts until the next network event.
resolvectl status is the view that tells the truth: per-link DNS servers, routing domains, and whether the tunnel link still exists. resolvectl query example.com goes further and shows which interface answered. Use both before touching a file, and keep in mind that a browser with its own secure DNS ignores all of it.
Find the owner before you edit anything
Three components usually compete for the same setting: systemd-resolved, NetworkManager, and whatever the VPN client installed. Identify the live one first:
systemctl is-active systemd-resolved
nmcli connection show --active
ps aux | grep -E "openvpn|wg-quick|vpn"
If resolved is active and the symlink points at its stub, the global file is generated output and your edit is cosmetic. If NetworkManager owns the connection, the servers belong to the connection profile, so the fix is an nmcli property change rather than a file edit. If a vendor daemon is running, its own restore path is the only one that survives the next connect.
Clear the link state the client left behind
A killed client cannot run its cleanup hook. SIGKILL cannot be caught at all, while SIGINT and SIGTERM can, so a tunnel that "just stopped" after a force-kill often leaves both the interface registration and the resolver entry in place. Find the specific PID and signal that one:
ps aux | grep -E "openvpn|wg-quick"
sudo kill <pid>
resolvectl revert tun0
Do not reach for pkill -f vpn or killall vpn before reading the full match list. -f matches the whole command line, so it can end a wrapper script, an editor session or an unrelated process whose path contains the string. Ending the wrong process mid-run is how a resolver problem becomes a lost file.
resolvectl revert resets the runtime DNS configuration of that link, which is narrower than restarting the resolver and much narrower than editing the global file. If the tunnel link is gone entirely, systemctl restart NetworkManager rebuilds the connection state and is the step the source threads keep returning to. Watch for a service manager with Restart=always, because it can put a killed client, and its DNS settings, straight back.
The other direction: pushed DNS that never arrived
The mirror image of the stale entry is a tunnel with no usable resolver at all. A DNS option in the profile is a request; the component that manages the connection has to hand it to the resolver. If the log shows the server was received but the tunnel link carries no DNS settings, the integration is missing, not the server.
Test the handoff by doing it manually, which separates "the server is wrong" from "nobody applied it":
resolvectl dns tun0 10.10.10.10
resolvectl domain tun0 ~.
The catch-all routing domain sends every name to that link, which is what a full-tunnel client wants and what split DNS must not use. These settings vanish on the next connect. To make them persistent in NetworkManager, disable automatic DNS, set the server on the connection and add the domain:
nmcli connection modify office ipv4.ignore-auto-dns yes
nmcli connection modify office ipv4.dns 10.10.10.10
nmcli connection modify office ipv4.dns-search "~."
For a company tunnel that should answer only internal names, list those zones with a leading tilde instead of the catch-all, so public names keep using the local resolver.
Workarounds that become the next outage
Copying resolv.conf before connecting and restoring it afterwards is a legitimate line in a runbook, and it is a workaround rather than a repair. It fails exactly when it matters, because a crash or a forced reboot skips the restore. Write it down as a workaround so the next person does not build on it.
Disabling systemd-resolved and hardcoding public servers is the other popular move. It makes connect and disconnect predictable and removes per-link DNS for every other network, which breaks split DNS for internal names and defeats the purpose of a tunnel meant to keep queries inside it. Installing resolvconf and pinning public servers costs the same with an extra layer. Treat either one as a diagnostic: if it restores browsing, you have learned that the resolver integration is at fault, not that the public servers are correct.
The durable fix is one owner per layer: decide which component owns the tunnel link's DNS and which owns the global default, write that down, and remove the others from the path. Then verify after a disconnect with a name only the VPN resolves. If a private name still answers, the registration was not removed; if nothing resolves at all, the file points at a server that no longer exists. Both are visible in resolvectl before you open a browser.