Why DNS Security Deserves Your Attention in 2026
DNS underpins every network connection your Linux servers make — and yet it remains one of the most overlooked attack vectors in production environments. Think about it: standard DNS queries travel in plaintext over UDP port 53, exposing every domain your infrastructure resolves to anyone positioned along the network path. Your ISP, a compromised router, an attacker on a shared network segment — they can all see everything. And without cryptographic verification, your resolvers just accept whatever response arrives first, making DNS spoofing and cache poisoning practical attacks that don't even require malware.
The urgency has intensified considerably this year. On March 15, 2026, CA/Browser Forum Ballot SC-085v2 took effect, requiring all Certificate Authorities to validate DNSSEC when present during domain control validation (DCV) and CAA record lookups before issuing TLS certificates. Simultaneously, Ballot SC-081v3 reduced maximum TLS certificate lifetimes to 200 days (dropping to 100 days in 2027 and 47 days by 2029). More renewals mean more DCV checks, and every DCV check now includes DNSSEC validation. A misconfigured DNSSEC deployment doesn't just weaken security — it blocks certificate issuance entirely and can take your domain offline.
That's a pretty serious consequence for something most teams treat as an afterthought.
This guide walks you through hardening DNS on Linux from the ground up: deploying Unbound as a secure recursive resolver, enabling DNSSEC validation, encrypting queries with DNS over TLS, implementing response policy zones as a DNS-layer firewall, and verifying everything actually works. Every configuration here is production-tested and designed for defense in depth.
Understanding DNS Threats on Linux Systems
Before diving into configuration, it helps to understand what you're actually defending against. DNS attacks fall into several categories that a hardened resolver needs to address:
- DNS Spoofing and Cache Poisoning: An attacker injects forged DNS responses, redirecting your servers to malicious IP addresses. Without DNSSEC validation, your resolver has no way to tell a legitimate response from a fabricated one.
- Man-in-the-Middle Eavesdropping: Standard DNS queries are sent in cleartext. Any network observer can see every domain your servers resolve, leaking information about your infrastructure, applications, and third-party dependencies.
- DNS Amplification and DDoS: Open or poorly configured recursive resolvers can be weaponized as amplification vectors in distributed denial-of-service attacks, generating response traffic 50-70x larger than the original query.
- DNS Rebinding: An attacker manipulates DNS responses to bypass same-origin policies, directing a domain to internal IP addresses to access services behind your firewall.
- DNS Tunneling and Data Exfiltration: Attackers encode stolen data within DNS queries to bypass traditional egress filtering, since port 53 traffic is rarely blocked or inspected.
A properly hardened DNS setup addresses all of these through a combination of cryptographic validation (DNSSEC), transport encryption (DNS over TLS), access controls, rate limiting, and policy-based filtering. So, let's get into it.
Setting Up Unbound as a Secure Recursive Resolver
Unbound is a validating, recursive, caching DNS resolver developed by NLnet Labs. It's purpose-built for security: DNSSEC validation and QNAME minimization are enabled by default, it runs in a chroot by default on most distributions, and its relatively small codebase reduces attack surface compared to full-featured DNS servers like BIND.
Unbound is the right choice when you need a resolver that validates DNSSEC, encrypts upstream queries, and serves your internal network — without the complexity of running an authoritative DNS server. I've been running it in production for years, and honestly, its stability is impressive.
Installation
Install Unbound on your Linux distribution:
# Debian/Ubuntu
sudo apt update && sudo apt install unbound unbound-anchor dns-root-data -y
# RHEL/Rocky/AlmaLinux 9
sudo dnf install unbound -y
# Arch Linux
sudo pacman -S unbound
Resolving the systemd-resolved Conflict
On Ubuntu and other systemd-based distributions, systemd-resolved occupies port 53 by default with its stub listener. Unbound can't bind to the same port. First, check whether you actually have a conflict:
sudo ss -tulpn | grep :53
If systemd-resolved is listening on 127.0.0.53:53, disable the stub listener without disabling the service entirely:
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo tee /etc/systemd/resolved.conf.d/no-stub.conf > /dev/null << EOF
[Resolve]
DNSStubListener=no
EOF
sudo systemctl restart systemd-resolved
# Point /etc/resolv.conf to Unbound
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
This is one of those things that trips people up constantly on fresh Ubuntu installs. The port conflict error isn't always obvious, so it's worth checking proactively.
Base Unbound Configuration
Create a hardened base configuration at /etc/unbound/unbound.conf.d/server.conf (or edit /etc/unbound/unbound.conf directly on RHEL-based systems):
server:
# Network interface binding
interface: 127.0.0.1
interface: ::1
port: 53
# Access control — restrict to trusted networks
access-control: 127.0.0.0/8 allow
access-control: ::1/128 allow
access-control: 10.0.0.0/8 allow
access-control: 172.16.0.0/12 allow
access-control: 192.168.0.0/16 allow
access-control: 0.0.0.0/0 refuse
access-control: ::/0 refuse
# DNSSEC validation
auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Privacy: QNAME minimization (default in modern Unbound)
qname-minimisation: yes
qname-minimisation-strict: no
# Hide server identity and version
hide-identity: yes
hide-version: yes
# Harden against protocol exploits
harden-glue: yes
harden-dnssec-stripped: yes
harden-below-nxdomain: yes
harden-referral-path: yes
use-caps-for-id: yes
# Cache tuning
msg-cache-size: 64m
rrset-cache-size: 128m
cache-min-ttl: 300
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
# Performance
num-threads: 2
so-reuseport: yes
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
# Logging (minimal for production)
verbosity: 1
log-queries: no
log-replies: no
logfile: ""
use-syslog: yes
Let me break down what those hardening options actually do, because the names aren't always self-explanatory:
- harden-glue: Requires glue records to be within the delegated zone, preventing cache poisoning through out-of-bailiwick glue.
- harden-dnssec-stripped: Requires DNSSEC data for trust-anchored zones. If DNSSEC data is absent when it should be present, the query fails rather than silently downgrading.
- harden-below-nxdomain: Returns NXDOMAIN for queries below an NXDOMAIN response, conforming to RFC 8020 and preventing certain cache poisoning attacks.
- use-caps-for-id: Randomizes the capitalization of query names (0x20 encoding), adding entropy that makes spoofing harder without affecting DNS resolution.
Enabling DNSSEC Validation
DNSSEC cryptographically signs DNS records at the source, creating a chain of trust from the DNS root zone down to individual domain records. Your resolver verifies each response using public keys published in the DNS hierarchy. If a record's been tampered with, validation fails and the response is rejected — the resolver returns SERVFAIL rather than serving poisoned data.
It's a simple concept, but getting it right matters more than ever.
Initializing the Trust Anchor
DNSSEC validation requires a root trust anchor — the public key of the DNS root zone. Unbound manages this through the unbound-anchor utility:
# Initialize the root trust anchor
sudo unbound-anchor -a /var/lib/unbound/root.key
# Verify the file was created
ls -la /var/lib/unbound/root.key
# Ensure correct ownership
sudo chown unbound:unbound /var/lib/unbound/root.key
Unbound automatically performs RFC 5011 trust anchor maintenance while running, updating the key when the root zone key is rolled. The auto-trust-anchor-file directive in the configuration enables this automatic update, so you don't need to worry about manual key management.
Fetching the Root Hints
Unbound needs the addresses of root DNS servers to begin recursive resolution. Most packages include a root hints file, but you can make sure it's current:
# Download current root hints
sudo curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache
# Add to Unbound config
# server:
# root-hints: "/etc/unbound/root.hints"
Verifying DNSSEC Is Working
Start Unbound, then test DNSSEC validation with known signed and intentionally broken domains:
# Start and enable Unbound
sudo systemctl enable --now unbound
# Test a DNSSEC-signed domain — should show the 'ad' flag
dig @127.0.0.1 example.com A +dnssec
# Test a deliberately broken DNSSEC domain — should return SERVFAIL
dig @127.0.0.1 dnssec-failed.org A
# Confirm the SERVFAIL is DNSSEC-related by disabling validation
dig @127.0.0.1 dnssec-failed.org A +cd
In the output of the first command, look for the ad (Authenticated Data) flag in the header section. This flag tells you Unbound validated the DNSSEC signatures successfully. The second command should return SERVFAIL because the domain's DNSSEC is intentionally broken — proving your resolver rejects tampered responses. The third command (with +cd to disable checking) should return a normal response, confirming the SERVFAIL was caused by DNSSEC validation rather than a network issue.
If you don't see the ad flag on the first test, something's wrong with your trust anchor setup. Go back and verify the root key file exists and has the correct ownership.
Encrypting DNS Queries with DNS over TLS
DNSSEC validates the integrity and authenticity of DNS responses, but it doesn't encrypt the queries themselves. Anyone on the network path between your resolver and upstream authoritative servers can still observe which domains you're resolving. DNS over TLS (DoT) wraps DNS traffic in a TLS session on port 853, providing confidentiality for your DNS queries.
You've got two deployment options here: configure Unbound to forward queries over TLS to a trusted upstream resolver, or run Unbound as a fully recursive resolver that does its own resolution. Each approach has trade-offs worth considering.
Option 1: Forward Over TLS to a Trusted Upstream
This is the simpler approach and works well when you trust an upstream provider like Cloudflare (1.1.1.1), Quad9 (9.9.9.9), or Google (8.8.8.8) to handle your DNS resolution. Add a forwarding zone to your Unbound configuration:
# Add to /etc/unbound/unbound.conf.d/forward-tls.conf
server:
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
# On RHEL: tls-cert-bundle: "/etc/pki/tls/certs/ca-bundle.crt"
forward-zone:
name: "."
forward-tls-upstream: yes
# Cloudflare
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
# Quad9 (malware-blocking)
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
The # suffix after each address specifies the TLS authentication name — Unbound verifies the upstream server's TLS certificate against this hostname. This is strict-mode DoT: if the certificate doesn't match, the query fails rather than falling back to unencrypted DNS. That's exactly the behavior you want.
Option 2: Full Recursive Resolution (No Forwarding)
If you don't want to trust any upstream provider (and I can understand why), run Unbound as a fully recursive resolver. In this mode, Unbound queries authoritative DNS servers directly, starting from the root. Queries to authoritative servers aren't encrypted (most authoritative servers still don't support DoT), but your DNS traffic is never centralized through a single provider:
# No forward-zone needed — Unbound resolves recursively by default
# Just ensure root-hints is configured:
server:
root-hints: "/etc/unbound/root.hints"
auto-trust-anchor-file: "/var/lib/unbound/root.key"
The trade-off here is pretty clear: full recursion gives you maximum privacy from upstream providers but doesn't encrypt queries to authoritative servers. Forwarding over TLS encrypts the full path to the forwarder but trusts that provider with all your DNS queries. For most production environments, forwarding over TLS to a trusted resolver is the pragmatic choice.
Verifying TLS Encryption
Confirm that DNS traffic is actually encrypted and not leaking on port 53:
# Install tcpdump if needed
sudo apt install tcpdump -y # or dnf install tcpdump
# Monitor for plaintext DNS traffic (should show nothing for forwarded queries)
sudo tcpdump -i any port 53 -c 10 &
# Generate a DNS query
dig @127.0.0.1 example.com A
# Check for TLS traffic on port 853
sudo tcpdump -i any port 853 -c 10 &
dig @127.0.0.1 example.net A
When forwarding over TLS, you should see traffic on port 853 and nothing on port 53 for upstream queries. Local queries from clients to Unbound on port 53 are expected — that's just the local client-to-resolver path.
Client-Side DNS over TLS with systemd-resolved
On workstations and servers that use a centralized DNS resolver elsewhere in the network, you can enable DoT directly in systemd-resolved without deploying Unbound. This encrypts the path between the local machine and its configured DNS resolver.
# Create a drop-in configuration
sudo mkdir -p /etc/systemd/resolved.conf.d
sudo tee /etc/systemd/resolved.conf.d/dns-tls.conf > /dev/null << EOF
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net
FallbackDNS=1.0.0.1#cloudflare-dns.com 149.112.112.112#dns.quad9.net
DNSOverTLS=yes
DNSSEC=allow-downgrade
Domains=~.
EOF
sudo systemctl restart systemd-resolved
# Verify the configuration
resolvectl status
The DNSOverTLS=yes setting enforces strict DoT — if the resolver doesn't support TLS, queries fail. Use DNSOverTLS=opportunistic if you need fallback to plaintext for reliability on networks that block port 853. Note that DNSSEC=allow-downgrade is used here because systemd-resolved's DNSSEC implementation is still considered incomplete — for full DNSSEC validation, stick with Unbound.
DNS Firewall with Response Policy Zones
Response Policy Zones (RPZ) turn your DNS resolver into a DNS-layer firewall. When a client queries a domain that matches an RPZ rule, Unbound can return NXDOMAIN, redirect to a sinkhole, or block the query entirely. This is surprisingly effective against malware command-and-control domains, phishing infrastructure, and DNS rebinding attacks.
Configuring RPZ in Unbound
Unbound supports RPZ through the rpz module. Create a local blocklist zone file:
# Create the RPZ zone file
sudo tee /etc/unbound/rpz.zone > /dev/null << EOF
\$TTL 3600
@ IN SOA localhost. root.localhost. (
2026032101 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
86400 ; minimum
)
@ IN NS localhost.
; Block known malware C2 domains
malware-domain.example.com CNAME .
phishing-site.example.com CNAME .
; Block DNS rebinding to private ranges
; (add entries for domains that resolve to RFC1918 addresses)
EOF
Then add the RPZ configuration to Unbound:
# Add to /etc/unbound/unbound.conf.d/rpz.conf
server:
module-config: "respip validator iterator"
rpz:
name: "rpz.local"
zonefile: "/etc/unbound/rpz.zone"
rpz-action-override: nxdomain
rpz-log: yes
rpz-log-name: "rpz-local"
For production environments, you'll want to subscribe to community-maintained RPZ feeds that are updated with known malicious domains. Sources like the OISD blocklist or Quad9's threat intelligence can be imported into your RPZ zone on a schedule using a cron job or systemd timer. This turns your resolver into a constantly updated security layer — well worth the minimal setup effort.
Rate Limiting and DDoS Mitigation
An improperly rate-limited DNS resolver can be weaponized in DNS amplification attacks. Even if your access controls restrict recursion to trusted networks, rate limiting provides defense in depth against compromised internal hosts and query floods.
# Add to Unbound server configuration
server:
# Limit the number of queries per second from a single IP
ip-ratelimit: 1000
# Limit total queries per second for all clients
ratelimit: 10000
# Size of the rate-limit cache
ip-ratelimit-size: 4m
ratelimit-size: 4m
# Limit large UDP responses to prevent amplification
max-udp-size: 4096
# Disable unnecessary features
do-not-query-localhost: yes
The ip-ratelimit setting caps queries per source IP, while ratelimit caps the total query rate to protect the resolver itself. Setting max-udp-size limits response sizes, reducing the amplification factor if the resolver gets abused. You'll want to adjust these values based on your traffic patterns — run unbound-control stats_noreset for a while to establish baselines before locking things down.
Hardening DNS at the OS Level
The resolver configuration is only as strong as the operating system it runs on. Don't skip these OS-level hardening measures — they complement the Unbound configuration and provide defense in depth.
Firewall Rules
Restrict DNS ports to only the networks that should access the resolver:
# nftables example — allow DNS only from internal networks
sudo nft add table inet dns_filter
sudo nft add chain inet dns_filter input "{ type filter hook input priority 0; policy drop; }"
# Allow DNS from trusted networks
sudo nft add rule inet dns_filter input ip saddr 10.0.0.0/8 udp dport 53 accept
sudo nft add rule inet dns_filter input ip saddr 10.0.0.0/8 tcp dport 53 accept
sudo nft add rule inet dns_filter input ip saddr 192.168.0.0/16 udp dport 53 accept
sudo nft add rule inet dns_filter input ip saddr 192.168.0.0/16 tcp dport 53 accept
# Allow localhost
sudo nft add rule inet dns_filter input ip saddr 127.0.0.0/8 udp dport 53 accept
sudo nft add rule inet dns_filter input ip saddr 127.0.0.0/8 tcp dport 53 accept
# Allow outbound DoT (port 853) for upstream queries
sudo nft add rule inet dns_filter input tcp sport 853 ct state established accept
Systemd Service Hardening
Override the Unbound systemd unit to add sandboxing. Most Unbound packages already include some of these, but it's worth verifying and extending them:
sudo systemctl edit unbound
# Add the following overrides:
[Service]
ProtectSystem=strict
ProtectHome=yes
PrivateDevices=yes
PrivateTmp=yes
NoNewPrivileges=yes
ReadWritePaths=/var/lib/unbound
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
AppArmor or SELinux Profile
If your distribution uses AppArmor (Ubuntu, SUSE) or SELinux (RHEL, Fedora), make sure the profile for Unbound is in enforcing mode:
# AppArmor — check status
sudo aa-status | grep unbound
# SELinux — verify context
ls -Z /usr/sbin/unbound
getenforce # Should return "Enforcing"
If the Unbound profile is in complain mode or disabled, you're missing an entire layer of containment. Fix that before moving on.
Testing and Validating Your DNS Security
A hardened DNS configuration is only as good as its verification. I can't stress this enough — don't just deploy and assume everything's working. Use these tools and tests to confirm every layer is doing its job.
DNSSEC Validation Testing
# dig: Check for the 'ad' (Authenticated Data) flag
dig @127.0.0.1 cloudflare.com A +dnssec +short
# delv: Purpose-built DNSSEC validation tool (BIND 9.10+)
delv @127.0.0.1 example.com A
# drill: DNSSEC chain chasing
drill -S example.com @127.0.0.1
# Test against a known-broken DNSSEC domain
dig @127.0.0.1 dnssec-failed.org A # Should return SERVFAIL
DNS over TLS Verification
# Verify no plaintext DNS leaks to upstream
sudo tcpdump -i eth0 port 53 -c 5 -w /dev/null 2>&1 &
dig @127.0.0.1 test-tls.example.com A
# If forwarding over TLS, you should see no port 53 upstream traffic
# Check TLS connection to upstream resolver
openssl s_client -connect 1.1.1.1:853 -servername cloudflare-dns.com < /dev/null 2>/dev/null | head -5
Unbound Health and Statistics
# Enable remote control (required for unbound-control)
sudo unbound-control-setup
# Add to Unbound config:
# remote-control:
# control-enable: yes
# View resolver statistics
sudo unbound-control stats_noreset
# Check cache status
sudo unbound-control dump_cache | head -20
# Verify configuration syntax before restarting
sudo unbound-checkconf
Online DNSSEC Testing
For domains you manage, validate your DNSSEC deployment using online tools before it affects certificate issuance:
- DNSViz (dnsviz.net) — Visual chain-of-trust analysis that shows exactly where validation breaks
- VeriSign DNSSEC Debugger (dnssec-analyzer.verisignlabs.com) — Automated diagnostics for DNSSEC-signed zones
- Zonemaster (zonemaster.net) — Comprehensive DNS zone health check including DNSSEC
These tools are particularly critical now that CA/B Forum Ballot SC-085v2 ties DNSSEC validation to TLS certificate issuance. A broken DNSSEC chain means no new certificates — test your zones well before renewal time, not after things break.
Monitoring DNS Security in Production
Hardening without monitoring is honestly a half-measure. You need ongoing DNS security monitoring to detect misconfigurations, attacks, and anomalies before they cause real problems.
# Enable query logging temporarily for analysis (disable in steady state)
# Add to Unbound config:
# server:
# log-queries: yes
# log-replies: yes
# verbosity: 2
# Monitor Unbound logs for DNSSEC validation failures
journalctl -u unbound -f | grep -i "SERVFAIL\|validation\|bogus"
# Set up a cron job to check DNSSEC health of your domains
# /etc/cron.daily/dnssec-check
#!/bin/bash
for domain in yourdomain.com api.yourdomain.com; do
result=$(dig @127.0.0.1 "$domain" A +dnssec 2>&1)
if ! echo "$result" | grep -q "flags.*ad"; then
echo "WARNING: DNSSEC validation issue for $domain" | \
logger -t dnssec-monitor -p auth.warning
fi
done
For enterprise environments, forward Unbound logs to your SIEM platform. Use structured logging or a log enrichment tool like LAUREL to transform DNS log data into JSON that can be correlated with network flow data and endpoint telemetry. Set alert thresholds for NXDOMAIN spikes (potential tunneling or DGA malware), unusual query volumes from single hosts (potential data exfiltration), and SERVFAIL clusters (potential DNSSEC misconfiguration or attack).
The CA/B Forum DNSSEC Mandate: What You Need to Do
The March 2026 changes deserve special attention because they create a direct operational link between DNS security and TLS certificate availability. Here's what SC-085v2 means for your infrastructure in practical terms:
- If your domain uses DNSSEC: The CA now validates your DNSSEC signatures during every certificate issuance and renewal. If your DNSSEC chain is broken (expired signatures, DS/DNSKEY mismatch, incorrect delegation), the CA returns BOGUS and rejects the certificate request. No certificate gets issued until you fix the DNSSEC problem.
- If your domain doesn't use DNSSEC: No change. The CA continues DCV exactly as before. SC-085v2 doesn't require DNSSEC — it requires CAs to respect DNSSEC when present.
- Combined with shorter certificate lifetimes: With SC-081v3 reducing certificate lifetimes to 200 days (2026), 100 days (2027), and 47 days (2029), your certificates will renew far more frequently. Each renewal triggers a DCV with DNSSEC validation. A domain with 47-day certificates faces roughly 8 DNSSEC checks per year per certificate — leaving very little margin for broken delegation or expired keys.
Action items: audit your DNSSEC deployment now using DNSViz, automate key rollovers with your DNS provider, monitor DS record propagation, and make sure your ACME client (Certbot, acme.sh, Lego) handles DNSSEC-related failures with proper retry logic and alerting. Don't wait for a failed renewal to discover the problem.
Complete Production Configuration Reference
Here's a consolidated Unbound configuration combining all the hardening measures covered in this guide. Adjust network ranges, cache sizes, and rate limits to match your environment:
# /etc/unbound/unbound.conf
server:
interface: 127.0.0.1
interface: ::1
# interface: 10.0.0.1 # Uncomment for LAN resolver
port: 53
# Access control
access-control: 127.0.0.0/8 allow
access-control: ::1/128 allow
access-control: 10.0.0.0/8 allow
access-control: 0.0.0.0/0 refuse
access-control: ::/0 refuse
# DNSSEC
auto-trust-anchor-file: "/var/lib/unbound/root.key"
root-hints: "/etc/unbound/root.hints"
# Privacy
qname-minimisation: yes
hide-identity: yes
hide-version: yes
# Hardening
harden-glue: yes
harden-dnssec-stripped: yes
harden-below-nxdomain: yes
harden-referral-path: yes
harden-algo-downgrade: yes
use-caps-for-id: yes
aggressive-nsec: yes
# TLS for upstream forwarding
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
# Rate limiting
ip-ratelimit: 1000
ratelimit: 10000
ip-ratelimit-size: 4m
ratelimit-size: 4m
max-udp-size: 4096
# Cache
msg-cache-size: 64m
rrset-cache-size: 128m
cache-min-ttl: 300
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
# Performance
num-threads: 2
so-reuseport: yes
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
# RPZ module
module-config: "respip validator iterator"
# Logging
verbosity: 1
use-syslog: yes
log-queries: no
log-replies: no
# Minimize information leakage
do-not-query-localhost: yes
val-clean-additional: yes
# DNS over TLS forwarding
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
# RPZ blocklist
rpz:
name: "rpz.local"
zonefile: "/etc/unbound/rpz.zone"
rpz-action-override: nxdomain
rpz-log: yes
rpz-log-name: "rpz-local"
# Remote control
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
Validate and apply the configuration:
# Check configuration syntax
sudo unbound-checkconf
# Restart Unbound
sudo systemctl restart unbound
# Verify it is running and resolving
dig @127.0.0.1 example.com A +dnssec +short
sudo unbound-control status
Frequently Asked Questions
Does DNSSEC slow down DNS resolution?
Not in any meaningful way. Research by ISC and APNIC shows that DNSSEC validation adds approximately 1 ms of latency to 1-2% of queries on a busy resolver. Over 90% of queries are served from cache within 1 ms regardless of DNSSEC. The main overhead is a roughly 10% increase in memory usage due to cached signatures. Using ECDSA keys instead of RSA further reduces response sizes and processing time.
What happens if DNSSEC is misconfigured on my domain?
Honestly, a misconfigured DNSSEC deployment is worse than no DNSSEC at all. If your DNSSEC chain of trust is broken — through expired signatures, DS/DNSKEY mismatches, or incorrect delegation — validating resolvers return SERVFAIL for all queries to your domain. And since March 2026, CA/B Forum Ballot SC-085v2 also means CAs will refuse to issue or renew TLS certificates for domains with broken DNSSEC. Always test with DNSViz and the VeriSign DNSSEC Debugger before and after making any changes.
Should I use DNS over TLS or DNS over HTTPS?
For server infrastructure, DNS over TLS (DoT) on port 853 is generally the better choice. It uses a dedicated port, making it easier to monitor and manage with firewall rules. DNS over HTTPS (DoH) runs on port 443, which makes it harder to distinguish from regular HTTPS traffic — useful for privacy on client devices but it complicates network security monitoring on servers. On Linux servers, Unbound natively supports DoT forwarding with simple configuration, whereas DoH requires additional setup or a proxy.
Can I run Unbound alongside systemd-resolved?
Yes, but you need to disable the systemd-resolved stub listener to avoid port conflicts. The recommended approach is to disable the stub listener with DNSStubListener=no in a resolved.conf drop-in, then point /etc/resolv.conf to 127.0.0.1 where Unbound listens. Alternatively, you can disable systemd-resolved entirely with systemctl disable --now systemd-resolved and manage /etc/resolv.conf manually. Either way works — just don't try to have both listening on port 53.
How do I protect my DNS resolver from being used in amplification attacks?
Three layers of protection work together: access controls restrict recursion to trusted networks only (access-control directives in Unbound), rate limiting caps queries per source IP and total throughput (ip-ratelimit and ratelimit), and response size limiting reduces the amplification factor (max-udp-size). Additionally, apply firewall rules to block inbound DNS from untrusted networks. And whatever you do, never run an open recursive resolver accessible from the internet — that's basically handing attackers a weapon.