adllm Insights logo adllm Insights logo

Mastering Per-Interface DNS Configuration with systemd-resolved on Linux

Published on by The adllm Team. Last modified: . Tags: systemd-resolved DNS Linux Networking systemd-networkd NetworkManager resolvectl DevOps

Modern Linux systems increasingly rely on systemd-resolved for managing network name resolution. This service, detailed in the systemd-resolved.service(8) man page, offers sophisticated capabilities, including the crucial ability to configure DNS settings on a per-interface basis. This means you can direct DNS queries for one network interface (e.g., a corporate VPN) to specific internal DNS servers, while another interface (e.g., your primary Ethernet) uses different, public DNS servers. This level of control is essential for developers, system administrators, and anyone dealing with complex network environments, multiple network connections, or specific privacy requirements.

This article provides an in-depth, practical guide to configuring systemd-resolved to use specific DNS servers for individual network interfaces on Linux. We’ll explore methods using systemd-networkd, NetworkManager, and the resolvectl utility, complete with actionable code examples and troubleshooting advice.

Understanding systemd-resolved and Per-Interface DNS

systemd-resolved acts as a central DNS stub resolver for local applications. It manages /etc/resolv.conf (ideally by having it symlinked to /run/systemd/resolve/stub-resolv.conf, which points to systemd-resolved’s own stub listener at 127.0.0.53) and provides features like caching, DNSSEC validation, and, importantly, per-link DNS configuration. For more on the traditional resolver configuration, see the resolv.conf(5) man page.

When per-interface DNS servers are defined, systemd-resolved prioritizes them for queries associated with that specific interface. This association can be based on the source interface of a query or, more powerfully, through “routing domains.”

Key Concepts:

  • Global DNS: Fallback DNS servers used if no per-interface configuration applies. Typically set in /etc/systemd/resolved.conf, as described in resolved.conf(5).
  • Per-Link DNS: DNS servers assigned to a specific network interface (e.g., eth0, wlan0, tun0).
  • Routing Domains: Specified with the Domains= setting.
    • Domains=example.com: Makes example.com a search domain and routes queries for *.example.com to this interface’s DNS.
    • Domains=~example.com: Routes queries for *.example.com to this interface’s DNS without making it a search domain (routing-only).
    • Domains=~.: A powerful setting that routes all queries not matching a more specific routing domain on another interface through this interface’s assigned DNS servers. This effectively makes the interface’s DNS primary for general resolution when active.

Method 1: Using systemd-networkd

If you manage your network interfaces with systemd-networkd (see systemd-networkd.service(8)), configuring per-interface DNS is straightforward and integrated. You define these settings within .network files located in /etc/systemd/network/, as specified in the systemd.network(5) man page.

Configuration Steps:

  1. Identify or Create a .network File: Each interface managed by systemd-networkd needs a corresponding .network file. For an interface named eth1, you might create /etc/systemd/network/10-eth1.network. The [Match] section identifies the interface.

  2. Add DNS Settings in the [Network] Section:

    • DNS=: A space-separated list of IP addresses for your desired DNS servers.
    • Domains=: Define search or routing domains.

Example: Configuring eth1 for Internal DNS

Let’s say eth1 connects to a private network requiring specific DNS servers (10.0.0.53, 10.0.0.54) for the internal.corp domain and as general resolvers for traffic through this interface.

Create or edit /etc/systemd/network/10-eth1.network:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# /etc/systemd/network/10-eth1.network
[Match]
Name=eth1

[Network]
Address=10.0.1.10/24 # Example static IP, or use DHCP
# Gateway=10.0.1.1   # If needed

# Per-interface DNS configuration
DNS=10.0.0.53 10.0.0.54
Domains=~internal.corp ~.

# Disable systemd-networkd from accepting DNS servers from DHCP
# if you want to enforce ONLY the servers listed above for this interface.
# This applies if DHCP is enabled on this interface (e.g. DHCP=yes
# or DHCP=ipv4). For static configurations as above, it's less
# critical but good practice.
UseDNS=false

Explanation:

  • DNS=10.0.0.53 10.0.0.54: Sets the specific DNS servers for eth1.
  • Domains=~internal.corp ~.:
    • ~internal.corp: Routes all queries for *.internal.corp to 10.0.0.53 and 10.0.0.54.
    • ~.: Makes these DNS servers the default for any other queries originating from or routed through eth1, if no other more specific domain match is found on other interfaces.
  • UseDNS=false: If this interface gets its IP via DHCP, this prevents DHCP-supplied DNS servers from being used by systemd-networkd for this interface, ensuring your manually specified DNS= servers are authoritative.

Applying and Verifying systemd-networkd Changes

  1. Apply Configuration: Restart systemd-networkd or reload its configuration:

    1
    
    sudo systemctl restart systemd-networkd
    

    If the interface was already up, you might need to bring it down and up, or re-evaluate its configuration:

    1
    2
    3
    
    sudo networkctl reconfigure eth1
    # or if using older methods to bring interfaces up/down:
    # sudo ifdown eth1 && sudo ifup eth1
    
  2. Verify with resolvectl: Check the DNS settings for the interface using the resolvectl(1) man page as reference:

    1
    
    resolvectl status eth1
    

    This should list 10.0.0.53 and 10.0.0.54 as current DNS servers for eth1 and show the configured domains.

    Test a query:

    1
    2
    3
    4
    5
    6
    
    # This query should go through eth1's DNS servers
    resolvectl query host.internal.corp
    
    # This query should also use eth1's DNS if eth1 is preferred for
    # general resolution due to the "~." domain.
    resolvectl query -i eth1 example.com
    

Method 2: Using NetworkManager

NetworkManager is a popular dynamic network control and configuration system that also works closely with systemd-resolved. You can learn more about it from the NetworkManager official website. Per-interface DNS settings are configured through its connection profiles, typically using nmcli (command-line utility, see nmcli(1) man page) or graphical tools like nm-connection-editor.

Configuration Steps with nmcli:

  1. Identify the Connection Name: List your NetworkManager connections:

    1
    
    nmcli connection show
    

    Find the name of the connection profile associated with the interface you want to configure (e.g., “Wired connection 1”, “MyVPN”).

  2. Modify DNS Settings: Use nmcli connection modify to set DNS servers and search/routing domains. You’ll likely also want to tell NetworkManager to ignore automatically configured (e.g., DHCP-provided) DNS servers for this connection.

Example: Configuring “MyOfficeVPN” Connection

Suppose you have a VPN connection profile named “MyOfficeVPN” and want it to use DNS servers 192.168.1.10 and 192.168.1.11 for the domain office.example.net and for all other traffic routed via this VPN.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Set specific DNS servers for the "MyOfficeVPN" connection
nmcli connection modify MyOfficeVPN ipv4.dns "192.168.1.10 192.168.1.11"

# Set routing domains. "~office.example.net" for specific domain
# routing, and "~." to make these DNS servers default for this connection.
nmcli connection modify MyOfficeVPN ipv4.dns-search \
  "~office.example.net ~."

# Tell NetworkManager to ignore DNS servers from DHCP or other
# auto-configs for this connection, prioritizing your manual settings.
nmcli connection modify MyOfficeVPN ipv4.ignore-auto-dns yes

# For IPv6, similar properties exist:
# nmcli con mod MyOfficeVPN ipv6.dns "2001:db8::10 2001:db8::11"
# nmcli con mod MyOfficeVPN ipv6.dns-search "~office.example.net ~."
# nmcli con mod MyOfficeVPN ipv6.ignore-auto-dns yes

Important: The ipv4.dns-search property when used with systemd-resolved can take the ~domain syntax for routing domains.

Applying and Verifying NetworkManager Changes

  1. Apply Configuration: Reactivate the connection for the changes to take effect:

    1
    2
    
    nmcli connection down MyOfficeVPN
    nmcli connection up MyOfficeVPN
    

    Alternatively, for some settings, a reload might be sufficient:

    1
    2
    
    nmcli connection reload MyOfficeVPN
    # Followed by re-activating if needed
    
  2. Verify with resolvectl: After the connection is active, check its DNS status:

    1
    2
    
    # Replace 'vpn0' with the actual interface name for MyOfficeVPN
    resolvectl status vpn0
    

    Test a query:

    1
    
    resolvectl query payroll.office.example.net
    

Method 3: Using resolvectl (Dynamic/Temporary Configuration)

The resolvectl command-line utility can directly set per-interface DNS settings. However, these settings are typically not persistent across reboots or interface restarts unless a script or service re-applies them. This method is useful for testing, temporary configurations, or scripting.

Configuration Commands:

  • resolvectl dns <interface> <server1> [server2] ...: Sets DNS servers for <interface>.
  • resolvectl domain <interface> <domain1> [domain2] ...: Sets search/routing domains for <interface>. Use ~. for routing all traffic.
  • resolvectl revert <interface>: Clears manually set DNS settings for <interface>, reverting to settings provided by other managers (like NetworkManager or systemd-networkd) or DHCP.

Example: Temporarily Setting DNS for wlan0

To temporarily set wlan0 to use 9.9.9.9 and route all its DNS queries through it:

1
2
3
4
5
6
7
8
# Set DNS server for wlan0
sudo resolvectl dns wlan0 9.9.9.9

# Set wlan0 to be the default resolver for non-specific domains
sudo resolvectl domain wlan0 "~."

# Verify the changes
resolvectl status wlan0

This shows 9.9.9.9 as the current server and ~. as the domain.

To test:

1
2
# This query should now attempt to use 9.9.9.9 via wlan0
resolvectl query -i wlan0 anotherexample.com

To revert wlan0 to its previous configuration (e.g., managed by NetworkManager):

1
sudo resolvectl revert wlan0

Verifying Your Overall DNS Configuration

Regardless of the method used, these tools are crucial for verification:

  • resolvectl status [interface]: Your primary tool. Without an interface argument, it shows global status and all link statuses. With an interface name (e.g., resolvectl status eth0), it shows detailed DNS information for that specific link, including current DNS servers, DNSSEC status, and configured domains.
  • resolvectl query [--interface=<IFACE>] <hostname>: Performs a DNS query. Use --interface to test resolution via a specific interface’s settings.
  • cat /etc/resolv.conf: Check its contents. For systemd-resolved to function optimally with its stub listener, this file should typically be a symlink to /run/systemd/resolve/stub-resolv.conf and contain nameserver 127.0.0.53.
    1
    2
    3
    
    ls -l /etc/resolv.conf
    # Example output:
    # /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
    
  • journalctl -u systemd-resolved -f: Follow the live logs for systemd-resolved for troubleshooting. For more detail, you can temporarily increase its log level.

Key Global Configuration: /etc/systemd/resolved.conf

While this article focuses on per-interface DNS, it’s useful to know that global fallback DNS settings are configured in /etc/systemd/resolved.conf or via drop-in files in /etc/systemd/resolved.conf.d/. Refer to the resolved.conf(5) man page for all options.

An example /etc/systemd/resolved.conf.d/global-dns.conf might look like:

1
2
3
4
5
6
7
8
# /etc/systemd/resolved.conf.d/global-dns.conf
[Resolve]
# Fallback DNS servers if no per-link config is suitable
DNS=8.8.8.8 1.1.1.1
FallbackDNS=8.8.4.4 1.0.0.1
# Domains=~. # If you want global DNS to handle all unrouted queries
DNSSEC=allow-downgrade
DNSOverTLS=opportunistic

These global settings are used if a query doesn’t match any specific per-interface routing rules or if the matched interface has no DNS servers defined.

Common Challenges and Troubleshooting

  • /etc/resolv.conf Mismanagement: Directly editing /etc/resolv.conf when it’s managed by systemd-resolved will lead to lost changes. Ensure it’s correctly symlinked.
  • Conflicting Network Managers: If both systemd-networkd and NetworkManager are active, they might compete to configure the same interface. It’s generally best to let only one manage a particular interface’s network settings.
  • DHCP Overwrites:
    • For systemd-networkd: In the .network file’s [DHCPv4] or [DHCPv6] section, ensure UseDNS=false if you want to ignore DHCP-provided DNS servers in favor of manually specified ones.
    • For NetworkManager: Use nmcli connection modify <conn_name> ipv4.ignore-auto-dns yes (and similarly for ipv6).
  • Caching: systemd-resolved caches responses. If you’re not seeing updated results immediately after a change, flush the caches:
    1
    
    sudo resolvectl flush-caches
    
  • Order of Domains= Entries: The order can matter, especially if you have overlapping domains or a mix of search and routing domains on the same interface. More specific routing domains are generally preferred.
  • Understanding ~. Behavior: Domains=~. on an interface makes its DNS servers handle all queries not explicitly routed elsewhere. If multiple active interfaces have ~., systemd-resolved uses metrics and interface states to pick one, which can sometimes be non-obvious. Be deliberate when using ~..

Advanced Topics: DNS-over-TLS (DoT) and DNSSEC

systemd-resolved also supports DNS-over-TLS (DoT) and DNSSEC validation, which can be configured globally in resolved.conf or, importantly, on a per-interface basis. This allows you, for instance, to enforce DoT for a specific Wi-Fi connection that routes to a public DoT-enabled resolver.

  • DoT: Set DNSOverTLS=yes (enforce) or DNSOverTLS=opportunistic in the [Resolve] section of resolved.conf or the [Network] section of a .network file.
  • DNSSEC: Set DNSSEC=yes (validate and fail if invalid), DNSSEC=no, or DNSSEC=allow-downgrade (validate if possible, otherwise accept).

These settings enhance security and privacy and can be tailored to the trust level of the network each interface connects to. The Arch Wiki page on systemd-resolved often contains practical tips on these advanced features.

Conclusion

Configuring per-interface DNS servers with systemd-resolved offers a powerful way to manage network name resolution on modern Linux systems. Whether you use systemd-networkd for static server-like configurations, NetworkManager for dynamic client setups, or resolvectl for quick tests, systemd-resolved provides the underlying framework for fine-grained control.

By understanding how to define DNS servers and routing domains for each network link, you can ensure that applications use the correct resolvers for specific corporate networks, VPNs, or privacy-enhancing services, making your Linux system more flexible, secure, and adaptable to diverse networking environments. Remember to always verify your changes using resolvectl status and resolvectl query to ensure your DNS resolution behaves as intended.