Self-Hosting Knot Resolver with Docker Compose
What Is Knot Resolver?
Knot Resolver (kresd) is a recursive DNS resolver developed by CZ.NIC, the Czech domain registry. It’s the resolver behind Cloudflare’s 1.1.1.1 DNS service (Cloudflare uses a modified version) and the Turris routers. Unlike Unbound, which focuses on being a standalone recursive resolver, Knot Resolver emphasizes performance, modern protocol support, and extensibility through Lua modules.
Version 6 introduced YAML-based configuration, replacing the older Lua config format. It resolves queries recursively (walking the DNS hierarchy from root servers), validates DNSSEC by default, and can serve DNS-over-TLS and DNS-over-HTTPS natively.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 256 MB of free RAM
- Port 53 available (disable systemd-resolved if needed)
- Understanding of DNS concepts (DNS Explained)
Docker Compose Configuration
Create a directory for Knot Resolver:
mkdir -p /opt/knot-resolver && cd /opt/knot-resolver
Create a docker-compose.yml:
services:
knot-resolver:
image: cznic/knot-resolver:v6.2.0
container_name: knot-resolver
ports:
- "53:53/tcp"
- "53:53/udp"
- "853:853/tcp" # DNS-over-TLS (optional)
- "443:443/tcp" # DNS-over-HTTPS (optional)
volumes:
- ./config.yaml:/etc/knot-resolver/config.yaml:ro
- knot-cache:/var/cache/knot-resolver
restart: unless-stopped
healthcheck:
test: ["CMD", "kdig", "@127.0.0.1", "example.com"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
knot-cache:
Configuration File
Create /opt/knot-resolver/config.yaml:
# Knot Resolver v6 configuration
workers: 2
logging:
level: info
network:
listen:
- interface: 0.0.0.0@53 # Plain DNS (UDP + TCP)
- interface: 0.0.0.0@853 # DNS-over-TLS
kind: dot
# DNSSEC validation is enabled by default
# No additional configuration needed
# Cache configuration
cache:
size-max: 256m # Maximum cache size in memory
# Optional: forwarding instead of recursive resolution
# forward:
# - subtree: "."
# servers:
# - address: 1.1.1.1
# - address: 9.9.9.9
Start the stack:
docker compose up -d
Initial Setup
Test that Knot Resolver is working:
# Basic resolution
dig @127.0.0.1 example.com
# DNSSEC validation test
dig @127.0.0.1 sigfail.verteiltesysteme.net # Should return SERVFAIL
dig @127.0.0.1 sigok.verteiltesysteme.net # Should return A record
Point Your Network at Knot Resolver
Update your router’s DHCP settings to distribute your server’s IP as the primary DNS server, or configure individual devices.
Configuration
YAML Configuration Reference
| Option | Type | Default | Purpose |
|---|---|---|---|
workers | integer | auto | Number of resolver worker threads |
logging.level | string | info | Log verbosity: debug, info, warning, error, critical |
network.listen | list | lo@53 | Bind addresses and protocols |
cache.size-max | string | auto | Maximum memory for DNS cache |
forward | list | — | Upstream forwarders (disables recursive mode) |
Recursive Mode (Default)
By default, Knot Resolver operates as a full recursive resolver — it walks the DNS hierarchy from root servers down to authoritative nameservers. DNSSEC validation is enabled automatically. No upstream forwarders needed.
Forwarding Mode
To forward queries to an upstream DNS provider instead of resolving recursively:
forward:
- subtree: "."
servers:
- address: 1.1.1.1
- address: 9.9.9.9
Split DNS (Domain-Specific Forwarding)
Route specific domains to different resolvers:
forward:
# Internal domains → corporate DNS
- subtree: "corp.example.com"
servers:
- address: 10.0.0.53
# Home LAN → router
- subtree: "home.lan"
servers:
- address: 192.168.1.1
# Everything else → recursive resolution (no forward block for ".")
See Split DNS Setup for detailed patterns.
Performance Tuning
| Setting | Recommendation | Why |
|---|---|---|
workers | CPU count - 1 | Each worker handles queries independently |
cache.size-max | 50-75% of available RAM for DNS | Larger cache = fewer recursive lookups |
For a server with 4 GB RAM dedicated to DNS:
workers: 3
cache:
size-max: 2g
Advanced Configuration
DNS-over-TLS Server
Serve encrypted DNS to clients that support DoT (Android, iOS, many DNS clients):
network:
listen:
- interface: 0.0.0.0@53
- interface: 0.0.0.0@853
kind: dot
# TLS configuration
network:
tls:
cert-file: /certs/fullchain.pem
key-file: /certs/privkey.pem
Mount your certificates:
volumes:
- ./config.yaml:/etc/knot-resolver/config.yaml:ro
- /etc/letsencrypt/live/dns.yourdomain.com:/certs:ro
- knot-cache:/var/cache/knot-resolver
DNS-over-HTTPS Server
Serve DoH on port 443:
network:
listen:
- interface: 0.0.0.0@53
- interface: 0.0.0.0@443
kind: doh2
Requires the same TLS certificate configuration as DoT.
Management API
Enable the kresctl management API for monitoring and runtime control:
management:
interface: 127.0.0.1@5000
Query resolver status:
docker exec knot-resolver kresctl metrics
docker exec knot-resolver kresctl cache/stats
Aggressive NSEC (RFC 8198)
Knot Resolver implements aggressive NSEC caching by default — it uses cached NSEC records to synthesize negative responses without querying authoritative servers. This reduces query load and speeds up “domain does not exist” answers.
Reverse Proxy
Knot Resolver serves DNS on port 53 (and optionally DoT on 853, DoH on 443). A reverse proxy isn’t applicable for DNS traffic. For remote access, use Tailscale or WireGuard. See Remote Access for options.
If serving DoH, the HTTPS endpoint can optionally be placed behind a reverse proxy — but Knot Resolver handles TLS termination natively.
Backup
Knot Resolver’s persistent state is minimal:
| Data | Location | Backup |
|---|---|---|
| Configuration | config.yaml (host file) | File copy |
| DNS cache | knot-cache volume | Not needed — rebuilds automatically |
| TLS certificates | Host files | Back up with your cert management |
cp /opt/knot-resolver/config.yaml /opt/backups/knot-resolver-config-$(date +%F).yaml
See Backup Strategy for general guidance.
Troubleshooting
Container Exits With Config Error
Symptom: Container restarts repeatedly, logs show YAML parse error
Fix: Validate your YAML syntax. Common issues:
- Indentation must use spaces, not tabs
- Listen interfaces need the
@portsuffix kindvalues are case-sensitive:dot,doh2, notDoT,DoH
docker compose logs knot-resolver
Port 53 Already in Use
Symptom: bind: address already in use
Fix: Disable systemd-resolved:
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
DNSSEC Validation Failures for Valid Domains
Symptom: Some legitimate domains return SERVFAIL
Fix: Check the system clock — DNSSEC validation depends on accurate time. Ensure NTP is running:
timedatectl status
If the clock is correct, the domain may have genuinely broken DNSSEC records. Test with:
dig @127.0.0.1 example.com +dnssec +cd # cd flag disables DNSSEC validation
Slow Resolution at Startup
Symptom: First queries take 1-2 seconds after restart
Fix: Normal — the cache is empty after restart. Knot Resolver needs to build its cache by performing full recursive lookups. Performance improves within minutes as the cache fills.
Resource Requirements
| Resource | Value |
|---|---|
| RAM | ~50 MB base + configured cache size |
| CPU | Low to moderate — scales with worker count |
| Disk | ~90 MB container + cache (if disk-backed) |
| Network | Outbound to root/authoritative DNS servers |
Verdict
Knot Resolver is the performance-focused alternative to Unbound for recursive DNS. Its YAML configuration (v6+) is clean and readable, DNSSEC validation works out of the box, and native DoT/DoH serving means you can offer encrypted DNS without additional software. The fact that Cloudflare chose it as the engine behind 1.1.1.1 speaks to its performance at scale.
For most homelabs, Unbound is the simpler choice with more community documentation and Docker image options. Choose Knot Resolver if you want native DoT/DoH serving, need aggressive NSEC caching, or prefer YAML configuration over Unbound’s custom config format.
FAQ
Is Knot Resolver the same as Knot DNS?
No. Knot DNS is an authoritative DNS server (like PowerDNS). Knot Resolver is a recursive resolver (like Unbound). Both are developed by CZ.NIC, but they’re separate projects for different purposes.
Can Knot Resolver block ads?
Not natively. Knot Resolver is a resolver, not a DNS sinkhole. For ad blocking, use Pi-hole or AdGuard Home with Knot Resolver as the upstream recursive resolver.
Why choose Knot Resolver over Unbound?
Knot Resolver has native DoT/DoH/DoQ serving, aggressive NSEC caching (RFC 8198), and YAML configuration. Unbound has more community Docker images, simpler defaults, and wider homelab adoption. Both perform well for home use.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments