This guide covers security best practices and hardening techniques for Knot Resolver deployments.
DNSSEC validation is critical for preventing DNS spoofing and cache poisoning attacks.
Version 6.x:
dnssec:
enable: true
# Log validation failures for monitoring
validation_failure_log: true
# Automatic trust anchor updates
trust_anchor_update: true
Version 5.x:
modules = { 'dnssec' }
-- Log validation failures
dnssec.validation_failure_log = true
-- Automatic trust anchor updates
trust_anchors.auto_update = true
# Test with a known good domain
dig @127.0.0.1 example.com +dnssec
# Test with a known bad domain (should return SERVFAIL)
dig @127.0.0.1 dnssec-failed.org
# Check validation statistics
kresctl stats | grep dnssec # v6
sudo kresctl > stats() # v5
Manual Trust Anchor (if needed):
dnssec:
enable: true
trust_anchor: "/etc/knot-resolver/root.key"
Generate and verify trust anchor:
# Get root trust anchor
curl -o /etc/knot-resolver/root.key \
https://data.iana.org/root-anchors/root-anchors.xml
# Verify checksum
sha256sum /etc/knot-resolver/root.key
Version 6.x:
policy:
access_control:
# Allow internal networks
- network: "10.0.0.0/8"
action: "allow"
- network: "172.16.0.0/12"
action: "allow"
- network: "192.168.0.0/16"
action: "allow"
# Deny all others (default)
- network: "0.0.0.0/0"
action: "deny"
Version 5.x:
-- Allow internal networks
access_control('10.0.0.0/8', policy.ALLOW)
access_control('172.16.0.0/12', policy.ALLOW)
access_control('192.168.0.0/16', policy.ALLOW)
-- Deny all others
access_control('0.0.0.0/0', policy.DENY)
Version 6.x:
policy:
views:
# Internal clients get full resolution
- name: "internal"
match:
- network: "192.168.0.0/16"
forward:
- address: "192.168.1.10" # Internal DNS
# External clients get limited resolution
- name: "external"
match:
- network: "0.0.0.0/0"
forward:
- address: "1.0.0.1"
- address: "8.8.8.8"
policy:
# Only allow recursion for trusted networks
recursion_acl:
- network: "10.0.0.0/8"
action: "allow"
- network: "192.168.0.0/16"
action: "allow"
Version 6.x:
rate_limiting:
enable: true
limit: 100 # queries per second per IP
window: 1 # Time window in seconds
action: "drop" -- Drop excess queries
Version 5.x:
rate_limit = 100 -- queries per second per IP
Version 6.x:
rate_limiting:
enable: true
# Different limits for different networks
limits:
- network: "10.0.0.0/8"
limit: 200 # Higher limit for internal
- network: "192.168.0.0/16"
limit: 150
- network: "0.0.0.0/0"
limit: 50 # Lower limit for external
# Whitelist trusted IPs
whitelist:
- "192.168.1.1"
- "192.168.1.2"
# Blacklist known bad actors
blacklist:
- "198.51.100.0/24"
rate_limiting:
enable: true
# Limit response size
max_response_size: 4096 # bytes
# Drop queries without RD bit
require_rd_bit: true
# Limit ANY queries
block_any: true
# Limit TXT queries (often used in amplification)
txt_query_limit: 10
Version 6.x:
tls:
enable: true
listen:
- address: "0.0.0.0:853"
cert: "/etc/knot-resolver/certs/tls.crt"
key: "/etc/knot-resolver/certs/tls.key"
# Strong cipher suites
ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"
# Minimum TLS version
min_protocol: "TLSv1.2"
# Prefer server ciphers
prefer_server_ciphers: true
# Session tickets (disable for perfect forward secrecy)
session_tickets: false
Version 5.x:
modules = { 'tls' }
tls('0.0.0.0:853',
'/etc/knot-resolver/certs/tls.crt',
'/etc/knot-resolver/certs/tls.key')
tls_config {
ciphers = 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305',
min_protocol = 'TLSv1.2',
prefer_server_ciphers = true,
session_tickets = false
}
Generate Strong Certificates:
# Generate 2048-bit (or higher) RSA key
openssl genrsa -out tls.key 4096
# Generate certificate with 1-year validity
openssl req -new -x509 -key tls.key -out tls.crt \
-days 365 \
-subj "/CN=dns.example.com/O=Example Corp/C=US" \
-addext "subjectAltName=DNS:dns.example.com,DNS:resolver.example.com"
# Set proper permissions
chmod 600 tls.key
chmod 644 tls.crt
Certificate Auto-Reload (v6):
tls:
enable: true
auto_reload: true
reload_interval: 3600 # Check every hour
UFW (Ubuntu/Debian):
# Allow DNS from internal networks only
sudo ufw allow from 10.0.0.0/8 to any port 53 proto tcp
sudo ufw allow from 10.0.0.0/8 to any port 53 proto udp
sudo ufw allow from 192.168.0.0/16 to any port 53 proto tcp
sudo ufw allow from 192.168.0.0/16 to any port 53 proto udp
# Allow DoT
sudo ufw allow from 10.0.0.0/8 to any port 853 proto tcp
sudo ufw allow from 192.168.0.0/16 to any port 853 proto tcp
# Allow DoH
sudo ufw allow from 10.0.0.0/8 to any port 443 proto tcp
sudo ufw allow from 192.168.0.0/16 to any port 443 proto tcp
# Allow metrics (internal only)
sudo ufw allow from 127.0.0.1 to any port 8453 proto tcp
# Enable firewall
sudo ufw enable
firewalld (RHEL/CentOS):
# Create custom service for DNS
sudo firewall-cmd --permanent --new-service=knot-resolver
sudo firewall-cmd --permanent --service=knot-resolver --add-port=53/udp
sudo firewall-cmd --permanent --service=knot-resolver --add-port=53/tcp
sudo firewall-cmd --permanent --service=knot-resolver --add-port=853/tcp
sudo firewall-cmd --permanent --service=knot-resolver --add-port=443/tcp
# Add rich rules for source networks
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="knot-resolver" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.0.0/16" service name="knot-resolver" accept'
# Reload
sudo firewall-cmd --reload
# Allow DNS from trusted networks
iptables -A INPUT -p tcp --dport 53 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -s 192.168.0.0/16 -j ACCEPT
# Allow DoT
iptables -A INPUT -p tcp --dport 853 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 853 -s 192.168.0.0/16 -j ACCEPT
# Allow DoH
iptables -A INPUT -p tcp --dport 443 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -s 192.168.0.0/16 -j ACCEPT
# Drop all other DNS queries
iptables -A INPUT -p tcp --dport 53 -j DROP
iptables -A INPUT -p udp --dport 53 -j DROP
# Rate limit new connections
iptables -A INPUT -p tcp --dport 53 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 53 -m state --state NEW -m recent --update --seconds 1 --hitcount 20 -j DROP
Version 6.x (systemd override):
Create /etc/systemd/system/knot-resolver.service.d/override.conf:
[Service]
User=knot-resolver
Group=knot-resolver
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/cache/knot-resolver
Version 5.x:
# Ensure service runs as knot-resolver user
sudo systemctl edit knot-resolver
version: '3.8'
services:
knot-resolver:
image: docker.io/cznic/knot-resolver:6.1.0
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=64m
- /run:noexec,nosuid,size=64m
volumes:
- ./config.yaml:/etc/knot-resolver/config.yaml:ro
- ./cache:/var/cache/knot-resolver
- ./certs:/etc/knot-resolver/certs:ro
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
systemd limits:
Create /etc/systemd/system/knot-resolver.service.d/limits.conf:
[Service]
LimitNOFILE=65535
LimitNPROC=64
MemoryMax=2G
CPUQuota=200%
Docker resource limits:
services:
knot-resolver:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
Create RPZ file /etc/knot-resolver/malware.rpz:
; Malware domains
malware-domain.com CNAME .
bad-actor.net CNAME .
phishing-site.org CNAME .
; IP-based blocking
198.51.100.0/24 CNAME .
Configure RPZ:
Version 6.x:
policy:
rpz:
- file: "/etc/knot-resolver/malware.rpz"
action: "drop"
- file: "/etc/knot-resolver/ads.rpz"
action: "nxdomain"
Version 5.x:
modules = { 'rpz' }
rpz {
file = '/etc/knot-resolver/malware.rpz',
action = policy.DROP
}
rpz {
file = '/etc/knot-resolver/ads.rpz',
action = policy.NXDOMAIN
}
# Download threat intelligence feeds
curl -o /etc/knot-resolver/threats.rpz \
https://threat-intelligence-feed.example.com/rpz
# Set up automatic updates with cron
# /etc/cron.daily/update-rpz:
#!/bin/bash
curl -o /etc/knot-resolver/threats.rpz \
https://threat-intelligence-feed.example.com/rpz
systemctl reload knot-resolver
Version 6.x:
privacy:
qname_minimization: true
Version 5.x:
modules = { 'privacy' }
Version 6.x:
logging:
level: "notice" # Don't log individual queries
dnstap:
log_queries: false
Version 5.x:
log_level = 'notice'
# Don't add identifying information
server:
hide_version: true
hide_hostname: true
Version 6.x:
logging:
level: "notice"
# Log security-relevant events
groups:
- "dnssec" # DNSSEC validation failures
- "policy" # Access control decisions
- "network" # Network errors
monitoring:
metrics: true
port: 8453
# Alert thresholds (external monitoring)
alerts:
high_query_rate: 1000 # queries/second
high_nxdomain_rate: 100 # NXDOMAIN/second
high_servfail_rate: 50 # SERVFAIL/second
# prometheus-alerts.yml
groups:
- name: knot-resolver
rules:
- alert: HighQueryRate
expr: rate(resolver_request_total[1m]) > 1000
for: 5m
annotations:
summary: "High DNS query rate detected"
- alert: DNSSECValidationFailures
expr: rate(resolver_dnssec_invalid_total[5m]) > 10
for: 5m
annotations:
summary: "DNSSEC validation failures detected"
- alert: RateLimitTriggered
expr: rate(resolver_rate_limited_total[1m]) > 100
for: 5m
annotations:
summary: "Rate limiting is actively dropping queries"
# Check trust anchors
kresctl dnssec check # v6
# View validation statistics
kresctl stats | grep dnssec
# Check system time (critical for DNSSEC)
timedatectl status
# Check rate limit statistics
kresctl stats
# Temporarily increase limits for testing
# Edit config.yaml and increase rate_limiting.limit
# Test DoT connection
kdig @localhost example.com +tls +tls-ca=/etc/knot-resolver/certs/tls.crt
# Check certificate validity
openssl x509 -in /etc/knot-resolver/certs/tls.crt -text -noout
# Verify certificate dates
openssl x509 -in /etc/knot-resolver/certs/tls.crt -dates -noout
Questions? Find all contact information on our contact page.