Systemd-resolved DNS configuration for VPN



Some guidance about configuring/fixing domain name resolution with a corporate Virtual Private Network (VPN), especially OpenVPN and with systemd-based Linux systems. This configuration uses the internal/private corporate resolvers for resolving internal/private domain names while using the ISP resolver for general domain names. This might help if your VPN is struggling these days because of the COVID-19 threat 😷.

I've been helping some people to make corporate VPNs (OpenVPN-based) work correctly, since yesterday. The problem is the resolution of domain names:

  1. either private internal domain names are not resolved because the internal corporate resolver is used;

  2. or general domain name resolution is slowish because the internal corporate resolver is used and the VPN is currently struggling with the new load.

A good solution is to use the internal coporate resolver for internal domain names but another (eg. the ISP) resolver for other domain names. Depending on your configuration and how you are running the VPN (CLI, NetworkManager, etc.), this might be already what is happening (you might find some information in this post in order to check this).

Below are some instructions about one solution to make this work on systemd-based Linux systems using systemd-resolved. There are solution to achieve this without systemd-resolved (I think it is possible to do that with dnsmasq).

Manual configuration


If you are using a Linux system with systemd-resolved, a quick fix is something like this (your need to adjust these values):

# Configure internal corporate domain name resolvers:
resolvectl dns tun0

# Only use the internal corporate resolvers for domain names under these:
resolvectl domain tun0 "~foo.example.com" "~bar.example.com"

# Not super nice, but might be needed:
resolvectl dnssec tun0 off

If you don't have the resolvectl commands, you might use instead:

systemd-resolve -i tun0 \
  --set-dns= --set-dns= \
  --set-domain=foo.example.com --set-domain=bar.example.com \
  --set-dnssec=off  # <- Not super nice, but might be needed.


First check if you have systemd-resolved installed and running:

systemctl status systemd-resolved

Check if you have the resolvectl too:


An alternative is to use systemd-resolve:

systemd-resolve --status

Check if have the resolve mechanism enabled in nssswitch.conf:

cat /etc/nsswitch.conf  | grep ^hosts: | grep resolve

Install the needed libraries. For example on Debian or Ubuntu AMD64 systems:

dpkg -l | grep libnss-resolve

Note: I am installing both the 64 bit and 32 bit libraries. If you do not install the 32 bit libraries, proper DNS resolution might fail for 32 programs.

Configure DNS resolution

If all these prerequisites are satisfied, you can configure the DNS resolution with something like:

resolvectl dns tun0
resolvectl domain tun0 "~foo.example.com" "~bar.example.com"

Alternatively, if you don't have resolvectl:

systemd-resolve -i tun0 \
  --set-dns= --set-dns= \
  --set-domain=~foo.example.com --set-domain=~bar.example.com

This configures systemd-resolved to use the corporate internal resolvers and for resolving all domain names under foo.example.com and bar.example.com (eg. some-service.foo.example.com). tun0 is the network interface reprensing the VPN. You have to adjust these parameters (IP address, domain names).

Note: the ~ in front of a domain prevents the domain to be added to the search list (i.e. trying to resolve the test host name will not search test.foo.example.com if a ~ was used in front of foo.example.com).

You can check your system-resolved configuration with:


If you don't have resolvectl:

systemd-resolve --status

You can now test domain name resolution (assuming some-service.foo.example.com actually exists):

resolvectl query some-service.foo.example.com

If you don't have resolvectl:

systemd-resolve some-service.foo.example.com

If this works, you should have:

some-service.foo.example.com:    -- link: tun0

-- Information acquired via protocol DNS in 1.7ms.
-- Data is authenticated: no

In my case, this failed because of:

some-service.foo.example.com: resolve call failed: DNSSEC validation failed: failed-auxiliary

A quick/dirty fix is to disabled DNSSEC:

resolvectl dnssec tun0 off

If you don't have resolvectl:

systemd-resolve -i tun0 \
  --set-dns= --set-dns= \
  --set-domain=foo.example.com --set-domain=bar.example.com \

Hooking with OpenVPN (CLI)

If you are using OpenVPN directly from the CLI (not through Network Manager), you can automate this with the --up options.

For example:

openvpn --config client.ovpn --script-security 2 --up ./manual-config

where ./manual-config is a shell script such as:

set -e
resolvectl dns $dev
resolvectl domain $dev "~foo.example.com" "~bar.example.com"
resolvectl dnssec $dev off


systemd-resolve -i $dev \
  --set-dns= --set-dns= \
  --set-domain=foo.example.com --set-domain=bar.example.com \
  --set-dnssec=off  # <- Not super nice, but might be needed.

OpenVPN sets the dev environment variable to the name of the VPN TUN/TAP (i.e. virtual) network device (eg. tun0) when calling the hooks.

Don't forget to make this script executable:

chmod u+x ./manual-config

Using update-systemd-resolved

For OpenVPN, an alternative is to use the update-systemd-resolved script:

openvpn \
  --config client.ovpn \
  --up /etc/openvpn/update-systemd-resolved \
  --down /etc/openvpn/update-systemd-resolved \
  --down-pre \

On Debian and Ubuntu, this script is in the openvpn-systemd-resolved package.

This automatically pulls the configuration advertised by the VPN server:

If this fails, the previous section might help you pinpoint to the problem:

  1. check the configuration (resolvectl);
  2. test internal/external names resolution (resolvectl query some-service.foo.example.com)
  3. disable DNSSEC if needed (resolvectl dnssec tun0 off).