After six months running systemd-resolved in production on Ubuntu 22.04, I finally understood why this service is built into systemd. At first I ignored it — left everything at defaults, as long as DNS resolved it was fine. But once I enabled DNS-over-TLS, everything changed: no more worrying about ISP tampering with DNS queries, faster resolution thanks to connection reuse and local caching, and DNSSEC to validate that domains are legitimate.
Quick Start: Configure in 5 Minutes
Open a terminal and follow these steps — no extra packages needed:
1. Check if systemd-resolved is running
systemctl status systemd-resolved
# If not running:
sudo systemctl enable --now systemd-resolved
2. Back up the old config and edit resolved.conf
sudo cp /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak
sudo nano /etc/systemd/resolved.conf
Add the following to the [Resolve] section:
[Resolve]
# DNS servers with DoT support — format: IP#hostname to verify TLS cert
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
FallbackDNS=8.8.8.8#dns.google 8.8.4.4#dns.google
# Enable DNS-over-TLS
DNSOverTLS=yes
# Enable DNSSEC validation
DNSSEC=yes
# DNS cache (enabled by default — kept here for clarity)
Cache=yes
3. Restart and verify
sudo systemctl restart systemd-resolved
# Check status — look for the DNS-over-TLS and DNSSEC lines
resolvectl status
If you see DNS-over-TLS setting: yes and DNSSEC setting: yes, the basic setup is complete.
4. Make sure /etc/resolv.conf points to the stub resolver
# Check current resolv.conf
cat /etc/resolv.conf
# Should contain: nameserver 127.0.0.53
# If not, create the symlink:
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Quick test:
resolvectl query itfromzero.com
# Or:
dig @127.0.0.53 itfromzero.com
Understanding the Problem: Why is DNS a Security Issue?
Traditional DNS sends queries over UDP port 53 in complete plaintext. That means anyone sitting between your machine and the DNS server can read which domains you’re looking up — your ISP, a public router at a café, or any device along the network path.
DNS-over-TLS (DoT) wraps all DNS queries inside a TLS connection, similar to how HTTPS wraps HTTP. Queries travel over TCP port 853, encrypted end-to-end. Your ISP can only see that you’re connecting to 1.1.1.1:853 — the query contents remain hidden.
Why use systemd-resolved instead of installing stubby or dnscrypt-proxy? Because it’s already available on every distro using systemd (Ubuntu, Fedora, Debian, Arch…), requires no extra packages, integrates well with NetworkManager, and handles caching out of the box.
Understanding the DNS#hostname Format
The IP#hostname syntax is used to validate the DNS server’s TLS certificate:
# Cloudflare DoT
DNS=1.1.1.1#cloudflare-dns.com
# Google DoT
DNS=8.8.8.8#dns.google
# Quad9 (also blocks malware domains)
DNS=9.9.9.9#dns.quad9.net
The #hostname part is the SNI (Server Name Indication) — resolved uses this hostname to verify the server’s TLS certificate. Without it, DoT still encrypts traffic but skips certificate verification, which is one step less secure.
Advanced: DNSSEC, Smart Fallback, and Per-link DNS
DNSSEC — Validate DNS Responses Against Spoofing
DNSSEC solves a different problem from DoT: it ensures DNS responses haven’t been forged (DNS spoofing/cache poisoning). With DNSSEC enabled, resolved verifies the digital signature of DNS records — if someone tries to inject a fake record, the query fails instead of returning a wrong address.
# Check DNSSEC is working
resolvectl query --type=DNSKEY google.com
# Test whether a specific domain has DNSSEC
resolvectl query --type=DS cloudflare.com
Note: If you use a VPN with its own DNS, DNSSEC=yes can sometimes cause conflicts. In that case, set DNSSEC=allow-downgrade — it validates when the server supports it and automatically falls back when it doesn’t.
Configuring Fallback DNS Properly
On the production Ubuntu 22.04 server with 4GB RAM that I manage, I found this significantly reduced processing time — especially when the primary DNS had latency spikes. I use Quad9 as the fallback because it automatically blocks malware domains, adding a free extra layer of protection:
[Resolve]
# Primary: Cloudflare DoT
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
# Fallback: Quad9 DoT — also blocks malware domains
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes
DNSSEC=yes
Cache=yes
DNSStubListener=yes
ReadEtcHosts=yes
Per-link DNS — Different DNS Per Interface
If your machine has multiple interfaces (VPN, LAN, WiFi) and you want each one to route DNS differently:
# View DNS assigned to each interface
resolvectl
# Assign custom DNS to eth0 (temporary, lost after reboot)
sudo resolvectl dns eth0 1.1.1.1
# Assign a search domain — the ~ prefix marks it as a routing domain
# Queries for *.company.internal will be routed through this interface
sudo resolvectl domain eth0 ~company.internal
The ~ prefix before a domain marks it as a routing domain: queries for company.internal are routed through this interface, while all other domains still use the global DNS. This is very useful when connected to a corporate VPN while still wanting regular internet traffic to go through a fast DNS server.
Practical Tips: Debugging and Monitoring
When DNS Fails to Resolve — Checklist
# View realtime logs
journalctl -u systemd-resolved -f
# Flush cache when you suspect stale entries
sudo resolvectl flush-caches
# View cache statistics — a good cache hit rate is above 40%
resolvectl statistics
# Query with verbose output for debugging
resolvectl query --legend=yes itfromzero.com
Verify DoT Is Actually Encrypting Traffic
# Check for active TCP:853 connections
ss -tnp | grep :853
# Capture traffic to confirm it's going over port 853
# (you won't see the contents because it's encrypted — that's a good sign)
sudo tcpdump -i any port 853 -n -c 20
Resolving Conflicts with /etc/resolv.conf
This is the most common issue. NetworkManager or a DHCP client can sometimes overwrite /etc/resolv.conf, causing DNS to completely bypass resolved.
# Check if resolv.conf is a symlink or a real file
ls -la /etc/resolv.conf
# If it's a real file — back it up and create the correct symlink:
sudo mv /etc/resolv.conf /etc/resolv.conf.bak
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
# If using NetworkManager — tell it not to overwrite resolv.conf:
sudo mkdir -p /etc/NetworkManager/conf.d
sudo tee /etc/NetworkManager/conf.d/dns.conf <<EOF
[main]
dns=systemd-resolved
EOF
sudo systemctl restart NetworkManager
After applying the full config, I use resolvectl statistics to monitor periodically. Cache hit rate on a dev machine is typically above 60% — more than half of all DNS queries never leave the local machine. On a production server with less domain diversity, that number climbs even higher.

