Short answer
You cannot have one /etc/resolv.conf that is both stubbed and static — but you don't need to. Keep the system-wide configuration exactly as recommended (symlink to /run/systemd/resolve/stub-resolv.conf) and give the legacy application its own resolver file via a private mount namespace or an application-level override. If that isn't possible, the supported fallback is to link /etc/resolv.conf to /run/systemd/resolve/resolv.conf, which still uses systemd-resolved's data but bypasses the 127.0.0.53 stub.
How the pieces actually work
systemd-resolved maintains two generated files under /run:
stub-resolv.conf — contains nameserver 127.0.0.53. Queries go to the local stub listener, which applies per-interface routing, caching, and (where configured) DNSSEC/DoT.
resolv.conf — contains the actual upstream servers resolved learned via DHCP or resolved.conf. Applications talk to upstreams directly, losing per-link routing and the cache.
So "non-stubbed but still using systemd-resolved as the backend" is precisely what the second file provides. The question is only which file each consumer reads.
Recommended configuration
Preferred, in order:
- Per-application override. Many legacy tools honor
LOCALDOMAIN/resolver environment settings or their own config (e.g., Java's sun.net.spi.nameservice properties, or a chroot/container with its own resolv.conf). Point only that app at /run/systemd/resolve/resolv.conf.
- Bind-mount for the app only. Run the legacy binary in a mount namespace (e.g., via a systemd service with
BindReadOnlyPaths=/run/systemd/resolve/resolv.conf:/etc/resolv.conf on unit versions supporting it) so the rest of the system keeps the stub.
- Whole-system fallback. If the app is unmanageable, switch the global link:
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf. systemd-resolved stays active and keeps updating that file; you lose per-interface routing and stub caching, but nothing fights the file.
What happens if /etc/resolv.conf becomes a regular file
systemd-resolved does not watch or repair /etc/resolv.conf. If you replace the symlink with a regular file, resolved simply keeps running and updating its /run files — your static file goes stale the moment DHCP leases change, VPNs connect, or networks roam. Worse, other managers (NetworkManager dispatcher scripts, resolvconf, cloud-init, VPN clients) may still rewrite it, producing unpredictable results. resolvectl status will report the resolv.conf mode as "foreign" rather than "stub" or "uplink". The conflict is not a crash; it is silent divergence.
Verification
readlink -f /etc/resolv.conf
resolvectl status
resolvectl query example.com
dig @127.0.0.53 example.com
ss -lunp | grep :53
The last command catches port-53 conflicts (dnsmasq, named, Docker) that would break the stub.
Caveats
Behavior is distro- and version-sensitive: RHEL's NetworkManager integration and defaults differ from Ubuntu's, and containers with bind-mounted or immutable /etc/resolv.conf cannot follow the symlink model at all. Confirm your resolved version (resolvectl --version) before relying on namespace features. If the legacy application is actually a container, the right fix is usually Docker/Kubernetes dnsServers configuration rather than anything on the host.