DNS keeps reverting, the VPN reconnect undoes your fix, and someone on the team eventually suggests locking the file. That sequence is a symptom, not a solution: two components both believe they own /etc/resolv.conf, and the one that writes last wins. Locking the file does not resolve the ownership conflict, it only hides it while breaking the component you actually wanted. Work the four checks below in order, and fix the arrangement at the level that owns it rather than at the file.

Check 1: is it a symlink, and to what

Start with the file itself, because the path you are editing may not be the file at all. A symlink into a resolver's runtime directory behaves completely differently from a plain file that a client script rewrites:

ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
cat /etc/resolv.conf

A link to a systemd-resolved stub path means the file's contents are generated, and hand-editing it will be overwritten at the next event. A plain file with nameserver lines is usually written by whichever client or DHCP hook runs on connect. Both are legitimate; what matters is knowing which one you have before you change anything.

Check 2: which daemon is running

Find out what is actually active rather than what is installed. Several resolvers can be present on a modern distribution, and the one holding port 53 on the loopback interface is the one answering:

systemctl status systemd-resolved --no-pager
resolvectl status
ss -lunp | grep ':53'

The status output names the per-link DNS servers and the routing domains, which is the map you need for split DNS. If internal names must go to an internal server and everything else to a public one, that policy lives in the per-link domains, and flattening it into a static nameserver list in the file silently loses the distinction.

NetworkManager's own configuration decides which of these backends it uses. Its dns= setting in the main configuration file selects between its internal resolver, dnsmasq, systemd-resolved and none, and a distribution may have chosen for you. Read that setting before deciding what "fixing DNS" means on this host.

Check 3: what the VPN client is allowed to write

This is where the conflict usually lives. A VPN client that rewrites the resolver file directly will fight any service that also maintains it, and the loser is whoever runs first. Recent OpenVPN releases can hand DNS settings to systemd-resolved instead of rewriting the file, which is the cooperative behaviour you want; older scripts and hand-written hooks often call a legacy helper or simply overwrite the file.

WireGuard's situation is similar in shape: the interface configuration itself does not manage DNS, and the tooling around it does, through a resolver helper or the resolved integration. If two such helpers are installed, both will try to apply their view of the world at connect time.

Read the client's up and down hooks rather than guessing. A few lines of shell in /etc/openvpn/up.sh or an equivalent directory explain behaviour that otherwise looks random, and they are also the place to make the client cooperate instead of compete.

Check 4: fix it at the owner, not the file

Once you know the owner, configure DNS through that owner's supported interface. For systemd-resolved that means per-link settings and routing domains rather than a static file; for NetworkManager it means the connection profile's DNS settings and the chosen backend; for a distribution using a resolvconf integration, it means writing through that helper.

Do not make the file immutable. The immutable attribute stops legitimate DHCP renewals and VPN updates along with the ones you were fighting, and it leaves the system in a state where the next package upgrade or reconnect produces a failure nobody can explain from the configuration. It conceals the ownership conflict, which is the thing you were trying to resolve.

Where two components genuinely must coexist, give them different jobs. Let the network manager own the interface-level configuration, let the VPN client contribute per-link DNS and domains through the same integration, and remove the third script that writes the file directly. The systemd-resolved documentation sets out the modes it supports and what each one exposes in /etc/resolv.conf. Pick the mode that matches how much policy you need to keep. If split DNS matters, the stub arrangement with per-link domains preserves it, while the mode that copies upstream servers into the file quietly flattens it away.

Verify after a reconnect, not after the edit

A file that looks correct immediately after your change proves nothing, because the next event has not happened yet. Trigger the sequence that used to break it: bring the tunnel down and up, renew the DHCP lease or toggle the interface, then read the same values again.

resolvectl query build.corp.example
resolvectl query example.com
resolvectl status | head -40

Then confirm the distinction that matters: the internal name resolves through the internal server while the public name still works. A configuration that sends everything to the internal server looks fine until someone tries to reach the internet through the tunnel, and a configuration that sends everything to the public one leaks internal names.

Record the owner, the backend and the symlink mode in the deployment notes. That single paragraph turns a future package upgrade from an incident into a review item, and it stops the next person from rediscovering the same conflict with a chattr command.

ХӀокхунах тера йаззамаш