How Linux Manages Split DNS with systemd-resolved
Split DNS allows a Linux system to route DNS queries to different
nameservers based on the requested domain name, which is vital when
connected to private networks or VPNs while maintaining standard
internet access. In modern Linux distributions, this behavior is handled
natively by systemd-resolved. This article explains how
systemd-resolved tracks per-link network configurations,
utilizes routing domain prefixes, evaluates query scopes, and directs
domain requests to the appropriate nameservers seamlessly.
Understanding the Split DNS Problem
Traditional Linux DNS resolution relies on
/etc/resolv.conf, which applies nameservers globally. In
this legacy model, the resolver queries the listed servers sequentially.
If a corporate VPN adds its DNS server to the top of the list, that
server receives all DNS traffic—including non-work requests—raising
privacy and performance issues. Conversely, if it is placed at the
bottom, private hostnames fail to resolve if the primary public server
returns an NXDOMAIN (non-existent domain) error.
systemd-resolved solves this by introducing
per-interface DNS awareness directly into the network stack.
Per-Interface DNS Configuration
Instead of maintaining a single global set of DNS servers,
systemd-resolved associates DNS servers and domain
configurations with specific network interfaces (such as
eth0, wlan0, or a VPN interface like
tun0 or wg0).
Each interface can define two types of domains:
- Search Domains: Suffixes appended to single-label
queries (e.g., querying
intranetbecomesintranet.company.internal). - Routing Domains: Domains used exclusively to direct queries to a specific interface's DNS server, without automatically appending them to search lists.
The Routing Domain Syntax
(~)
The core mechanism for split DNS in systemd-resolved is
the routing domain syntax. When defining domains for a specific link,
prefixing a domain name with a tilde (~) designates it
strictly as a routing domain.
- Example: Setting
Domains=~corp.example.comon a VPN interface tellssystemd-resolvedthat any query ending in.corp.example.commust be directed exclusively to the DNS servers associated with that VPN interface. - Catch-all Fallback: An interface configured with
Domains=~.acts as a fallback routing domain for all traffic not matched by a more specific rule.
Query Routing Logic
When an application makes a DNS query via the local stub listener
(127.0.0.53), systemd-resolved evaluates the
destination domain against all known link configurations using the
following rules:
- Most Specific Match Wins: If a query matches a
defined routing or search domain, it is routed to the interface with the
most specific match. For example, a query for
service.dev.corp.example.comwill prefer an interface with~corp.example.comover an interface assigned the default catch-all~.. - Parallel Queries (Fallback): If multiple interfaces
match with equal specificity,
systemd-resolvedsends queries to all matching interfaces in parallel, returning the first valid answer received. - Loopback Scope: Single-label hostnames or local names matching mDNS or LLMNR configurations bypass interface-specific routing rules and are handled by their respective protocols if enabled.
Managing Split DNS with
resolvectl
System administrators and VPN clients (like OpenVPN, WireGuard, or
NetworkManager) control this behavior dynamically via the
resolvectl command-line utility.
To configure split DNS manually for an interface named
tun0:
Assign nameservers to the interface:
resolvectl dns tun0 10.0.0.2 10.0.0.3Assign routing domains to the interface:
resolvectl domain tun0 ~company.internal ~corp.example.comVerify the routing configuration:
resolvectl status tun0
This configuration ensures that any request targeting
*.company.internal or *.corp.example.com is
routed through 10.0.0.2 or 10.0.0.3 over
tun0, while all other internet requests continue through
the default gateway's DNS servers.
Integration with NetworkManager and VPN Clients
Most modern network managers integrate automatically with
systemd-resolved. When a VPN connection establishes:
- NetworkManager receives the pushed DNS settings and routing domains from the VPN server.
- It communicates these settings to
systemd-resolvedvia D-Bus. systemd-resolveddynamically assigns the routes and cleans them up automatically as soon as the interface is torn down.
Through this per-link architecture and domain-matching logic,
systemd-resolved delivers deterministic, secure, and
automated split DNS routing across complex multi-homed Linux
environments.