Adding per‑interface DNS search domains to systemd‑resolved on Ubuntu 24.04

Why per‑interface search domains matter

If a box has more than one NIC—like a wired LAN that talks to a corporate DNS and a Wi‑Fi that talks to a public ISP—mixing the two can bite you. A query for server will be sent to the DNS server of the interface that sent the request, but the search list that the resolver appends is taken from the global configuration unless you tell it otherwise. That means you can end up asking your public DNS for an internal host or, even worse, leaking internal names to the Internet. Per‑interface search domains keep the resolver tidy and stop accidental leakage.

Prerequisites

Ubuntu 24.04 ships with systemd‑resolved as the default resolver and systemd‑networkd for static interfaces. NetworkManager can also drive systemd‑resolved. The examples below assume you have root access and that systemd-resolved is active (systemctl status systemd-resolved shows “active (running)”).

sudo systemctl enable --now systemd-resolved

The default /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf. Keep that symlink; editing the file directly will be overwritten.

Using systemd‑networkd

Create a drop‑in directory for per‑interface settings:

sudo mkdir -p /etc/systemd/networkd.conf.d

For each interface, add a .network file. Example for eth0 that should use the corporate DNS and search domain corp.example.com:

# /etc/systemd/networkd.conf.d/eth0.network
[Match]
Name=eth0

[Network]
DNS=10.0.0.1
Domains=corp.example.com

Similarly, for wlan0 that should use the ISP’s DNS and search domain home.net:

# /etc/systemd/networkd.conf.d/wlan0.network
[Match]
Name=wlan0

[Network]
DNS=8.8.8.8
Domains=home.net

Reload networkd and systemd‑resolved:

sudo systemctl restart systemd-networkd
sudo systemctl restart systemd-resolved

Why this works

systemd‑resolved consults the Domains= key in the .network file for the interface that receives the query. The search list is then limited to that domain, and the DNS server chosen is per‑interface, so queries never leave the intended network.

Using NetworkManager

If you prefer NetworkManager, you can set the search domain in the connection editor or via nmcli.

# For eth0
nmcli con modify eth0 ipv4.dns-search "corp.example.com"
nmcli con up eth0

# For wlan0
nmcli con modify wlan0 ipv4.dns-search "home.net"
nmcli con up wlan0

NetworkManager writes these settings to /etc/NetworkManager/system-connections/*.nmconnection. The dns-search key is translated into the Domains= option that systemd‑resolved consumes.

Verifying the configuration

Run systemd-resolve --status to see per‑interface settings:

$ systemd-resolve --status
Global
  DNS Servers: 8.8.8.8
  DNSSEC supported: yes
  DNSSEC enabled: no
  DNSSEC trust anchors: 0
  DNSSEC supported: yes

Interface eth0
  Current DNS Server: 10.0.0.1
  DNS Servers: 10.0.0.1
  Domains: corp.example.com

Interface wlan0
  Current DNS Server: 8.8.8.8
  DNS Servers: 8.8.8.8
  Domains: home.net

Test resolution with dig or systemd-resolve:

systemd-resolve -i eth0 -d corp.example.com server
systemd-resolve -i wlan0 -d home.net server

Both should return the IP address from the respective DNS servers.

Security considerations

  1. Split DNS – By keeping internal and external DNS servers separate, you prevent internal hostnames from leaking to the public DNS. This is especially important in hybrid environments where a public interface might inadvertently send queries to the internal DNS.

  2. DNSSEC – Enable DNSSEC on the internal DNS if possible. systemd‑resolved can enforce DNSSEC per‑interface by adding DNSSEC=yes in the .network file. This ensures that only authenticated responses are accepted.

  3. Least privilege – If you only need a search domain for a single interface, avoid global Domains= entries in /etc/systemd/resolved.conf. Keep the global search list empty (Domains=) and rely on per‑interface files.

  4. Avoid DNS over UDP – For sensitive environments, consider configuring systemd‑resolved to use DNS over TLS (DOT) per interface by adding DNSOverTLS=yes in the .network file. This protects queries from eavesdropping on untrusted networks.

Common pitfalls and troubleshooting

SymptomLikely causeFix
systemd-resolve --status shows no DNS server for an interfaceInterface not matched by any .network fileVerify Name= matches the actual interface name (ip link show).
Queries resolve to wrong domainGlobal Domains= still set in /etc/systemd/resolved.confClear the global entry (Domains=) or set it to ~ to inherit from interfaces.
/etc/resolv.conf is a regular fileAnother tool (e.g., resolvconf) overwrote the symlinkRemove the file and recreate the symlink: sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf.
DNSSEC validation failsInternal DNS not signed or trust anchors missingDisable DNSSEC for that interface (DNSSEC=no) or add the proper trust anchor.
systemd-resolve shows “DNSSEC supported: yes” but queries failThe DNS server does not support DNSSECVerify the server’s configuration or disable DNSSEC for that interface.

Use journalctl -u systemd-resolved for detailed logs if resolution stalls.

Trade‑offs

  • systemd‑networkd gives you fine‑grained control and is ideal for servers or static setups. It requires editing files directly but is lightweight.
  • NetworkManager is more user‑friendly for desktops or laptops that switch between networks. It automatically updates systemd‑resolved but can be less transparent.
  • If you need to push DNS settings to a large fleet, consider using systemd-networkd with templated .network files or a configuration management tool; that keeps everything declarative.

The key takeaway? Don’t rely on a single global search list when you have multiple NICs. Explicit per‑interface settings keep name resolution predictable, secure, and easier to debug.

TAGS: linux dns systemd network security


See also