I’ve been using systemd-resolved as the default DNS stub resolver on most of my Linux servers for a while now. It’s lightweight, integrates well with NetworkManager, and when configured properly, it can do DNS-over-TLS (DoT) with built-in fallback and DNSSEC validation. In this post, I’ll walk through how I set it up for secure resolution, especially focusing on the StubListener feature and how to make sure validation actually works.
First, let’s make sure resolved is active. On most modern distros, it’s already running, but you can check:
systemctl is-active systemd-resolved
If it’s not enabled, start and enable it:
sudo systemctl enable --now systemd-resolved
By default, resolved uses unicast DNS from /etc/resolv.conf, which points to 127.0.0.53. That’s the stub listener. To upgrade this to DoT, you need to configure upstream DNS servers that support TLS, like Cloudflare (1.1.1.1) or Quad9 (9.9.9.9), and tell resolved to use TLS for them.
The main config file is /etc/systemd/resolved.conf. I usually make a backup first, then edit it:
sudo cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak
sudo nano /etc/systemd/resolved.conf
Inside, I uncomment and set the following:
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net
FallbackDNS=8.8.8.8 8.8.4.4
DNSOverTLS=yes
DNSSEC=yes
The DNS line specifies the upstream servers with their hostname for TLS validation. The #hostname part is critical — it tells resolved to verify the TLS certificate matches that name, preventing downgrade or spoofing attacks. FallbackDNS ensures that if the DoT servers are unreachable, it falls back to regular UDP/TCP (still validating DNSSEC if enabled). I keep DNSOverTLS=yes to enforce TLS where possible, and DNSSEC=yes to validate signatures.
After saving, restart the service:
sudo systemctl restart systemd-resolved
Now, check if DoT is actually being used. The best way is to query the stub resolver and check the AD (Authenticated Data) flag, which indicates DNSSEC validation succeeded:
systemd-resolve --status
Look for:
- Current DNS Server: 1.1.1.1
- DNSSEC Supported: yes
Then test resolution:
systemd-resolve example.com
If you see "DNSSEC validation: yes" in the output, you’re good. You can also check the journal for TLS handshake success:
journalctl -u systemd-resolved -f
One thing I’ve seen trip people up is assuming that DNSOverTLS=yes means only DoT. It doesn’t — it’s opportunistic. If the TLS handshake fails, it falls back to plain DNS unless you set DNSOverTLS=opportunistic or enforce it via firewall rules. For stricter setups, I sometimes block port 53 outbound except to the DoT IPs, but that’s advanced and can break captive portals.
Another useful feature is StubListener. By default, resolved listens on 127.0.0.53:53. But you can make it listen on other interfaces too — say, if you want to run a local DoT forwarder for other devices on your network. To enable that, add:
[Resolve]
...\nListeners=127.0.0.53 192.168.1.10
Then restart. Now, other devices can point to 192.168.1.10:53 and get DoT-protected, DNSSEC-validated resolution through your host. I use this on my home lab gateway.
Finally, verify everything with a tool like drill or dig +tls:
drill @127.0.0.53 example.com
Check the ad flag in the answer section. If it’s set, DNSSEC validation passed.
In my environment, this setup gives me confidence that DNS queries aren’t being tampered with, especially on untrusted networks. It’s lightweight, doesn’t require extra proxies like stubby, and works seamlessly with DHCP and VPNs. If you’re running Linux servers or even a desktop and care about DNS integrity, giving systemd-resolved a proper DoT and DNSSEC tune-up is worth 10 minutes of your time.
As I mentioned before in my post about systemd slices (https://furkanikkan.com/urun/systemd-slice-ile-bellek-ve-io-sinirlamasi-kaynak-izolasyonu-rehberi-88), systemd components are often underutilized — but when tuned, they solve real problems cleanly.
Cover image: personalgraphic.official · CC0 (Openverse / kamu malı) · https://www.flickr.com/photos/198895458@N04/53097628210
