I recently audited DNS traffic on a few public-facing servers and noticed plain UDP/53 queries leaking to upstream resolvers. That’s when I decided to harden name resolution at the system level using systemd-resolved. It’s the built-in stub resolver on most modern distros and supports DNS-over-TLS (DoT) and DNSSEC validation out of the box.
Check if systemd-resolved is active
First, verify the service is running. On Ubuntu/Debian, it’s usually enabled by default, but minimal installs or custom setups may have it disabled.
systemctl is-active systemd-resolved
If it returns inactive, start and enable it:
sudo systemctl start systemd-resolved
sudo systemctl enable systemd-resolved
Then check its status:
sudo systemctl status systemd-resolved
You should see it listening on 127.0.0.53:53 — the local stub resolver address.
Configure DNS-over-TLS servers
Edit /etc/systemd/resolved.conf to define upstream DoT resolvers. I prefer using Cloudflare and Quad9 for their privacy policies and DNSSEC support.
[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 #hostname syntax tells systemd-resolved to validate the TLS certificate against that name — this is how DoT authentication works. Without it, you get encryption but no server identity verification.
After saving, restart the service:
sudo systemctl restart systemd-resolved
Verify DNS-over-TLS is working
Use resolvectl to query the current DNS settings and test resolution:
resolvectl status
Look for:
- Current DNS Server: 1.1.1.1, 9.9.9.9
- DNSSEC Supported: yes
- DNS-over-TLS: yes
Then test a domain:
resolvectl query example.com
You should see a secure answer with the ad flag (authenticated data) if DNSSEC validation passed.
Validate DNSSEC with a known-bad domain
To confirm DNSSEC is actually validating, test a domain with a broken signature:
resolvengt query dnssec-failed.org
This should return SERVFAIL because the zone fails validation. If you get an answer, DNSSEC isn’t working.
Optional: Use local fallback for resolver conflicts
Some tools (like Docker or VPN clients) overwrite /etc/resolv.conf. To prevent conflicts, ensure the symlink points to systemd-resolved’s stub:
ls -l /etc/resolv.conf
It should point to /run/systemd/resolve/stub-resolv.conf. If not, fix it:
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Monitor DNS traffic with tcpdump
To double-check that queries are going over TLS, sniff port 853:
sudo tcpdump -i any port 853 -v
You should see TLS handshakes and encrypted DNS queries. No plain DNS on port 53 should leave the host unless you’ve explicitly allowed it.
Common pitfalls
- Local DNS overrides ignored: If you rely on
/etc/hostsor local dnsmasq, systemd-resolved may bypass them. Adjust theDNSStubListenersetting or useListenDNSin resolved.conf to bind to another interface. - After reboot, DNS breaks: Usually because
/etc/resolv.confgot overwritten. A NetworkManager dispatcher script or systemd path unit can help enforce the symlink. - Cloudflare timeout: Occasionally, 1.1.1.1#cloudflare-dns.com times out. Keep a reliable fallback like 9.9.9.9#dns.quad9.net.
Final thoughts
Using systemd-resolved for DoT and DNSSEC is one of the simplest ways to encrypt and validate DNS without adding extra proxies or agents. It’s lightweight, integrated with the OS, and works well in headless servers, containers, and VMs. I’ve been running this config on edge nodes and internal infrastructure for months — no leaks, no validation bypasses, and clear audit trails via journalctl.
If you’re managing Linux systems exposed to untrusted networks, this should be baseline. For more on securing service-to-service communication, see my earlier post on [Linux tc VoIP DSCP marking](https://furkanikkan.com/urun/linux-tc-ile-voip-icin-dscp-ef-isaretleme-ve-oncelik-kuyrugu-yapilandirmasi-91).
Cover image: USDAgov · PDM (Openverse / kamu malı) · https://www.flickr.com/photos/41284017@N08/52288964396
