Tuning Unbound DNS for High-Throughput Linux Workloads
Published:
18 Sep, 2026
Running Unbound as a high-volume local caching resolver or recursive gateway under heavy traffic quickly exposes kernel network stack bottlenecks, thread contention, and cache eviction overhead. When handling tens of thousands of queries per second (QPS), default out-of-the-box configurations suffer from dropped UDP packets, elevated query response latencies, and high context switching.
Kernel-Level UDP Buffer and Network Tuning
Before modifying Unbound configuration parameters, the Linux network subsystem must be tuned to prevent packet drops at the network socket layer. Under bursty traffic, small socket receive buffers cause immediate dropped frames at the UDP interface.
Apply these sysctl settings in /etc/sysctl.d/99-dns-performance.conf to expand memory windows and queue depths:
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.rmem_default = 33554432
net.core.wmem_default = 33554432
net.core.netdev_max_backlog = 10000
net.ipv4.udp_rmem_min = 16384
net.ipv4.udp_wmem_min = 16384Reload the kernel parameters immediately with:
sudo sysctl -p /etc/sysctl.d/99-dns-performance.confThreading, Memory Slabs, and Socket Distribution
Unbound relies on a multi-threaded architecture where lock contention can degrade throughput if slab counters are not aligned with CPU core topology. Slabs must be set to a power of 2 closest to the number of configured worker threads.
- num-threads: Set this to match physical core count (avoid SMT/hyperthreads for deterministic latency).
- so-reuseport: Setting this to yes enables the Linux SO_REUSEPORT socket option, distributing incoming UDP packets directly within the kernel across separate file descriptors for each worker thread, eliminating single-socket lock contention.
- *-slabs: Set msg-cache-slabs, rrset-cache-slabs, infra-cache-slabs, and key-cache-slabs to identical powers of 2 (e.g., 8 slabs for 8 threads, 16 slabs for 16 threads).
Cache Allocation and Aggressive Resource Prefetching
Unbound separates message headers from Resource Record Sets (RRsets). The standard sizing ratio requires allocating twice as much memory to the rrset-cache-size as to the msg-cache-size.
To prevent cache miss latency on expiring popular domains, enable proactive resolution via prefetch and prefetch-key. Furthermore, enabling RFC 8198 aggressive negative caching (aggressive-nsec) allows Unbound to synthesize NXDOMAIN responses directly from NSEC/NSEC3 records without querying authoritative nameservers.
| Directive | Recommended Setting | Impact |
|---|---|---|
| msg-cache-size | 512m to 2g | Stores parsed DNS headers and metadata |
| rrset-cache-size | 1g to 4g | Stores actual DNS answer payloads (2x msg-cache) |
| so-rcvbuf | 8m / 16m | Assigns explicit socket buffer memory to Unbound |
| prefetch | yes | Refreshes queries near TTL expiration automatically |
| prefetch-key | yes | Prefetches DNSKEYs ahead of expiration for DNSSEC validation |
| aggressive-nsec | yes | Reduces upstream traffic for nonexistent subdomains |
Optimized Production Configuration Block
Below is a hardened, production-ready server block configured for an 8-core, 16GB RAM dedicated resolver instance located in /etc/unbound/unbound.conf.d/performance.conf:
server:
verbosity: 1
interface: 0.0.0.0
interface: ::0
port: 53
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
# Performance and Concurrency
num-threads: 8
so-reuseport: yes
so-rcvbuf: 16m
so-sndbuf: 16m
outgoing-range: 8192
num-queries-per-thread: 4096
msg-cache-slabs: 8
rrset-cache-slabs: 8
infra-cache-slabs: 8
key-cache-slabs: 8
# Memory Allocations (Ratio: rrset = 2 * msg)
msg-cache-size: 1024m
rrset-cache-size: 2048m
key-cache-size: 128m
neg-cache-size: 64m
# Cache Tuning & Prefetching
cache-min-ttl: 60
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 86400
serve-expired-client-timeout: 1800
aggressive-nsec: yes
infra-host-ttl: 900
# Hardening & Security
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: noValidating Performance and Monitoring Packet Loss
Check for kernel-level UDP buffer overflows using netstat:
netstat -su | grep -E 'receive errors|buffer errors'If RcvbufErrors increments under heavy load, expand net.core.rmem_max and verify that so-rcvbuf in Unbound matches or exceeds the kernel window.
Extract live runtime cache hits, misses, and recursion times through unbound-control:
sudo unbound-control stats_noreset | grep -E 'total.num.queries|cache.hit|cache.miss|time.avg'Ensuring that total.cache.hit stays above 85% with an average recursion latency under 15ms confirms the prefetch and memory slab pipeline is balanced for production workloads.