Split DNS with systemd
I've had to set up VPNs for organizations in the past. Every time I'd set one up with an intranet domain, all DNS requests from users would be forwarded to the organization's DNS server. This worked and was simple but I didn't like it, because I'd have to manage more private data from users than I needed. What if we could avoid forwarding regular requests to the organization's DNS server? This is one of the applications of what is often called split DNS, and I'll show you an example here.
Note that what I'm about to describe doesn't apply to VPN you'd use for all your network traffic. In such a case, forwarding all DNS requests via the VPN is actually important.
Example with a corporate VPN
Let's say the internal domain is
intranet.example.com where users have access services
gitlab.intranet.example.com. There are different ways to achieve this. In
general, if you have many users and many services, setting up a DNS server for the
organization is the way to go. Unfortunately, after users have configured their system to
use that DNS server, that server receives all DNS requests, not just those for
gitlab.intranet.example.com. At least that's my experience with Windows or Linux.
Note that it is often possible to add what is called a "secondary DNS server". That server is used when the primary server is unavailable regardless of the domain of DNS queries. This feature is thus useless for split DNS.
Solution for Linux with systemd
systemd, now the most common init system, comes with systemd-resolved. Instead of editing
/etc/resolv.conf, you can configure the resolver with
For example, with the main interface named
enp0s3 and the VPN interface named
you can use the following command:
resolvectl domain tun0 ~intranet.example.com
Afterwards, only requests for
intranet.example.com and its subdomains are sent to the
DNS server via the VPN. Other requests are forwarded to the DNS server available via your
You can check the configuration with the command
Global [...] Link 2 (enp0s3) Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6 Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 192.0.2.1 DNS Servers: 192.0.2.1 2001:db8:aaaa:3 Link 3 (tun0) Current Scopes: DNS Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 2001:db8:bbbb:3 DNS Servers: 2001:db8:bbbb:3 DNS Domain: ~internal
If you want to be sure that it is correctly configured, you can ask
resolvectl to do
some queries. For example:
> resolvectl query example.com example.com: 2606:2800:220:1:248:1893:25c8:1946 -- link: enp0s3 126.96.36.199 -- link: enp0s3 -- Information acquired via protocol DNS in 5.3ms. -- Data is authenticated: no; Data was acquired via local or encrypted transport: no -- Data from: network
We can see here that it used the local Ethernet interface,
To be extra sure that it worked as expected, I monitored outgoing DNS requests with
Wireshark and observed the result of running
ping foo.example.com or
Use with WireGuard and OpenVPN
With WireGuard, if you use
[Interface] PrivateKey = ... Address = 2001:db8:bbbb:1 DNS = 2001:db8:bbbb:3 PostUp = resolvectl domain tun0 ~intranet.example.com [Peer] ...
I haven't tested split DNS with OpenVPN but it should be possible to have it run the
desired command with the
up option in the client configuration:
script-security 2 up resolvectl domain tun0 ~intranet.example.com
See this StackExchange question for more information.
The new systemd component, systemd-resolved, gives you the power to do split DNS over different network interfaces. This is useful because it reduces the amount of private data sent to the DNS server available via the VPN.
If you don't have systemd or need more control over your DNS requests, I would probably recommend using a local resolver such as Unbound or dnsmasq.