Modern Linux networking relies on fast, deterministic domain name resolution to connect distributed microservices, resolve package repositories, and authenticate against corporate directory services. In Ubuntu 24.04 and 22.04 LTS, name resolution is governed by systemd-resolved, a local caching and network-aware DNS stub resolver. Historically, Linux administrators managed nameservers through static entries in /etc/resolv.conf. However, in modern environments where dynamic DHCP interfaces, container bridges, and WireGuard or OpenVPN tunnels coexist simultaneously, static configuration breaks. In particular, remote enterprise workers require “Split-DNS”—routing internal corporate queries (e.g., *.corp.internal) through private VPN nameservers while directing public internet queries through fast local resolvers. Mastering systemd-resolved and the resolvectl command is essential for calibrating DNS caching and establishing leak-free split-DNS routing in Ubuntu.
Understanding systemd-resolved Architecture and the 127.0.0.53 Stub
In Ubuntu, /etc/resolv.conf is not a static text file; it is a symlink pointing to /run/systemd/resolve/stub-resolv.conf. Inside, the nameserver is declared as 127.0.0.53. The table below delineates the functional layers of this architecture:
| Resolution Layer | Network Address | Operational Role | Administrative Inspection Tool |
|---|---|---|---|
| Local Stub Resolver | 127.0.0.53:53 |
Listens locally on loopback; intercepts POSIX standard glibc queries. | resolvectl status |
| In-Memory Cache | Kernel memory | Caches positive and negative DNS responses to accelerate query times. | resolvectl statistics |
| Link-Specific DNS | Interface bound (e.g., eth0, wg0) |
Assigns dedicated nameservers to specific physical or virtual network adapters. | resolvectl dns <interface> |
| Routing Domains (~domain) | Routing filter rules | Routes specific domain queries strictly through designated interfaces. | resolvectl domain <interface> |
Querying and Auditing DNS State with resolvectl
The primary command-line tool for interacting with systemd-resolved is resolvectl (which supersedes legacy systemd-resolve).
1. Inspecting Global and Link-Specific Resolvers
resolvectl status
The console output summarizes global fallback servers followed by per-interface telemetry:
Link 2 (eth0):
Current Scopes: DNS
Protocols: +DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.168.1.1
DNS Servers: 192.168.1.1 1.1.1.1
DNS Domain: home.arpa
2. Testing Live Domain Lookups
To inspect the exact routing path, latency, and returned IP addresses for a specific hostname:
resolvectl query api.ubuntu.com
The output reports the resolved A and AAAA records, along with the specific network link and upstream nameserver that answered the request.
Configuring DNS Caching and Flush Management
systemd-resolved automatically maintains an internal cache of DNS resource records, honouring Time-To-Live (TTL) values while capping stale records.
1. Auditing Cache Hit Ratios
resolvectl statistics
Review the performance telemetry:
- Transactions: Total number of DNS queries evaluated.
- Cache Hits / Cache Misses: The efficiency percentage of your local resolver cache.
- DNSSEC: Secure verification pass/fail tallies.
2. Flushing the Local DNS Cache
When domain DNS records are updated or a server moves to a new IP address, purge stale records from memory:
sudo resolvectl flush-caches
Verify that cache entries reset to zero using resolvectl statistics.
Configuring Split-DNS Routing for Enterprise VPNs
When connecting to an enterprise VPN (e.g., WireGuard on wg0 or OpenVPN on tun0), standard configurations frequently encounter two major flaws: either all internet traffic is routed through a congested corporate gateway, or internal corporate hostnames fail to resolve because public nameservers are queried first. Split-DNS resolves internal hostnames over the VPN while routing public lookups over your local network.
1. Understanding the Tilde (~) Routing Domain Operator
In systemd-resolved, prefixing a search domain with a tilde (~) declares it as a Routing Domain rather than a standard search domain. When a domain is declared with a tilde (e.g., ~corp.internal), systemd-resolved routes any query ending in corp.internal exclusively to the nameservers assigned to that specific interface.
2. Applying Link-Specific Nameservers and Routing Domains
Assume your WireGuard VPN interface is named wg0 and your corporate DNS server is 10.50.0.2:
# Step 1: Assign the corporate DNS server strictly to the VPN interface
sudo resolvectl dns wg0 10.50.0.2 10.50.0.3
# Step 2: Bind the private corporate domains to the VPN interface using the tilde prefix
sudo resolvectl domain wg0 "~corp.internal" "~dev.internal"
# Step 3: Ensure the VPN interface does not claim default route for all public DNS
sudo resolvectl default-route wg0 no
3. Verifying Split-DNS Isolation
Test resolution across both namespaces:
- Query an internal database:
resolvectl query postgres.corp.internal. The output proves the request was routed strictly acrossLink wg0to10.50.0.2. - Query a public site:
resolvectl query github.com. The output proves the request was routed acrossLink eth0to your local high-speed gateway, bypassing the VPN tunnel entirely.
Configuring Global Defaults in /etc/systemd/resolved.conf
To establish persistent upstream resolvers and enable cryptographic DNS security (DNS-over-TLS), edit the primary configuration daemon file:
sudo nano /etc/systemd/resolved.conf
Populate the [Resolve] block with modern security parameters:
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google
FallbackDNS=9.9.9.9 149.112.112.112
Domains=~.
DNSSEC=allow-downgrade
DNSOverTLS=opportunistic
Cache=yes
CacheTimeSlice=300
DNSOverTLS=opportunistic: Encrypts DNS queries using TLS if the upstream provider supports it, falling back to standard plaintext port 53 if TLS handshakes fail.DNSSEC=allow-downgrade: Validates cryptographic DNSSEC signatures on supporting domains without breaking legacy un-signed domains.Domains=~.: Treats the configured global DNS servers as the preferred default route for unmatched public queries.
Restart the daemon to apply your changes:
sudo systemctl restart systemd-resolved
Troubleshooting Common systemd-resolved Faults
Resolve operational DNS stalls using these diagnostic checks:
- Broken /etc/resolv.conf Symlink: If third-party utilities overwrite
/etc/resolv.confwith static files, restore the native systemd symlink:sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf - Port 53 Binding Conflicts: If you are deploying local DNS servers like BIND9, Dnsmasq, or Pi-hole, they will conflict with
systemd-resolved‘s stub on port 53. Disable the local stub listener by settingDNSStubListener=noinside/etc/systemd/resolved.confand updating/etc/resolv.confto point directly to/run/systemd/resolve/resolv.conf.