Expert coverage of Unbound recursive resolver: unbound.conf tuning, DNSSEC validation, DoT/DoH encrypted DNS, module architecture (validator, iterator, cachedb, python), local zones, pfSense/OPNsense integration, Pi-hole upstream configuration, and qname minimisation. Use for \"Unbound\", \"unbound.conf\", \"recursive resolver\", \"pfSense DNS\", \"OPNsense DNS\", \"Pi-hole upstream\", \"qname minimisation\", \"Unbound DNSSEC\". Do NOT use for cross-vendor comparison, platform selection, or c...
Pro scans all 2 files and shows the line behind each finding
Scanned 9/24/2026
npx -y skills add chrishuffman5/domain-expert --skill unbound --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Unbound?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/chrishuffman5-unbound)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: unbound
description: "Expert coverage of Unbound recursive resolver: unbound.conf tuning, DNSSEC validation, DoT/DoH encrypted DNS, module architecture (validator, iterator, cachedb, python), local zones, pfSense/OPNsense integration, Pi-hole upstream configuration, and qname minimisation. Use for \"Unbound\", \"unbound.conf\", \"recursive resolver\", \"pfSense DNS\", \"OPNsense DNS\", \"Pi-hole upstream\", \"qname minimisation\", \"Unbound DNSSEC\". Do NOT use for cross-vendor comparison, platform selection, or category-wide architecture -- use the `dns` skill."
license: MIT
---
# Unbound
This skill covers Unbound (NLnet Labs), a high-performance recursive DNS resolver. Areas of expertise include:
- Multi-threaded architecture with shared cache and per-thread iterators
- `unbound.conf` configuration and performance tuning
- DNSSEC validation with automatic trust anchor management (RFC 5011)
- DNS over TLS (DoT) upstream forwarding and incoming DoH server (1.17+)
- Module pipeline architecture: validator, iterator, respip, cachedb, python, dynlib
- Local zones and local-data for split-horizon, internal resolution, and ad-blocking
- pfSense and OPNsense integration (default resolver since OPNsense 17.7)
- Pi-hole upstream configuration for DNSSEC + ad-blocking combination
- Serve-expired for DNS resilience during upstream outages
- Qname minimisation (RFC 7816) for privacy
## How to Approach Tasks
1. **Classify** the request:
- **Configuration** -- `unbound.conf` settings, forwarding, local zones, access control
- **Performance tuning** -- Thread count, cache sizing, prefetch, TCP fast open
- **Security** -- DNSSEC validation, DoT/DoH, access control, hardening
- **Integration** -- pfSense/OPNsense GUI config, Pi-hole upstream, systemd-resolved
- **Architecture** -- Load `references/architecture.md` for module pipeline and deployment patterns
2. **Identify deployment** -- Standalone recursive, forwarder to upstream, pfSense/OPNsense appliance, Pi-hole upstream, or stub/authority hybrid.
3. **Identify scale** -- Home/small office (1-100 clients), campus (1000+), or ISP-scale (100k+). Scale determines thread count, cache sizes, and architecture.
4. **Recommend** -- Provide specific `unbound.conf` configuration blocks with explanations.
## Core Architecture
### Multi-Threaded Design
- `num-threads`: worker threads handling queries in parallel (match CPU core count)
- Shared cache across threads (or per-thread with optional sync via `msg-cache-slabs`)
- Each thread runs independent iterator state machine for recursive lookups
- Infra cache: tracks per-server performance (RTT, lame detection)
- Key cache: DNSSEC key material for validated zones
### Module Pipeline
Each query traverses a chain of modules:
1. **validator**: DNSSEC validation (signature verification, chain of trust)
2. **iterator**: recursive resolution logic (root -> TLD -> authoritative)
3. **respip**: response IP policy (EDNS client subnet, policy filtering)
4. **cachedb**: external cache database (Redis) for shared cache across instances
5. **python**: Python module for custom query/response manipulation
6. **dynlib**: C dynamic library for high-performance custom logic
Module order configured via `module-config: "validator iterator"` (default).
## Core Configuration
### Essential Settings
```ini
server:
num-threads: 4 # match CPU cores
interface: 0.0.0.0 # listen address
port: 53
access-control: 10.0.0.0/8 allow # allow internal clients
access-control: 192.168.0.0/16 allow
access-control: 127.0.0.0/8 allow
# DNSSEC
auto-trust-anchor-file: "/var/lib/unbound/root.key"
root-hints: "/etc/unbound/root.hints"
# Privacy
hide-identity: yes
hide-version: yes
qname-minimisation: yes # RFC 7816
# Performance
prefetch: yes # refresh popular entries before TTL expiry
prefetch-key: yes # prefetch DNSKEY for DNSSEC
msg-cache-size: 128m
rrset-cache-size: 256m # should be 2x msg-cache-size
cache-min-ttl: 60 # floor for cached TTLs
cache-max-ttl: 86400
# Resilience
serve-expired: yes # return stale on upstream failure
serve-expired-ttl: 86400 # max stale age (24 hours)
# Hardening
harden-glue: yes
harden-dnssec-stripped: yes
harden-below-nxdomain: yes
use-caps-for-id: yes # 0x20 encoding for cache poisoning defense
```
### DoT Upstream Forwarding
```ini
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: 8.8.8.8@853#dns.google
forward-addr: 8.8.4.4@853#dns.google
```
### DoH Server (Incoming, 1.17+)
```ini
server:
interface: 0.0.0.0@443
https-port: 443
tls-service-key: "/etc/unbound/server.key"
tls-service-pem: "/etc/unbound/server.pem"
http-endpoint: "/dns-query"
```
Full encrypted path: clients connect via DoH -> Unbound resolves via DoT upstream.
### Local Zones and Overrides
```ini
# Internal hostname resolution
local-zone: "home.lab." static
local-data: "server1.home.lab. IN A 10.0.0.10"
local-data: "server2.home.lab. IN A 10.0.0.11"
# Block a domain
local-zone: "ads.example.com." refuse
# Redirect domain
local-zone: "override.example.com." redirect
local-data: "override.example.com. IN A 10.0.0.1"
```
### Conditional Forwarding
```ini
# Forward internal domain to corporate DNS
forward-zone:
name: "corp.internal."
forward-addr: 10.0.0.53
forward-addr: 10.0.0.54
# Forward reverse lookups to internal DNS
forward-zone:
name: "10.in-addr.arpa."
forward-addr: 10.0.0.53
```
## pfSense and OPNsense Integration
### OPNsense (Default Resolver)
- Unbound is default DNS resolver since OPNsense 17.7
- GUI: Services > Unbound DNS > General (enable, interfaces, access networks)
- Advanced settings via "Custom options" text box in GUI
- DNSBL integration: OPNsense supports DNS block lists via Unbound plugin
- DoT upstream: configurable in GUI (Services > Unbound DNS > DNS over TLS)
- DHCP registration: auto-registers DHCP leases as DNS entries
### pfSense
- Unbound is default resolver (Services > DNS Resolver)
- GUI for common settings; advanced via custom options
- DHCP integration: static mappings and leases registered automatically
- Host overrides: GUI-managed local-data entries
### Pi-hole + Unbound Stack
Recommended architecture for ad-blocking with full DNSSEC:
```
Client ──► Pi-hole (ad filter) ──► Unbound (recursive + DNSSEC)
│
Root/TLD/Auth
(full recursion)
```
Pi-hole handles ad/tracker filtering. Unbound performs full recursive resolution with DNSSEC validation. No upstream forwarder needed -- Unbound queries root servers directly.
Configuration for Pi-hole upstream:
```ini
server:
interface: 127.0.0.1
port: 5335 # non-standard port for Pi-hole
do-not-query-localhost: no
access-control: 127.0.0.0/8 allow
# Full recursion (no forwarding)
# Do NOT add forward-zone for "."
```
Pi-hole Custom DNS: set upstream to `127.0.0.1#5335`.
## Performance Tuning
### Thread and Cache Sizing
| Deployment | Threads | msg-cache | rrset-cache |
|---|---|---|---|
| Home (1-10 clients) | 1-2 | 8m | 16m |
| Small office (10-100) | 2-4 | 32m | 64m |
| Campus (100-1000) | 4-8 | 128m | 256m |
| ISP (1000+) | 8-16 | 512m | 1g |
### Slab Configuration
Slabs should be a power of 2 close to `num-threads`:
```ini
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
```
### TCP Optimization
```ini
outgoing-range: 8192 # concurrent queries (per thread)
num-queries-per-thread: 4096
so-reuseport: yes # distribute sockets across threads
```
## Common Pitfalls
1. **rrset-cache-size < 2x msg-cache-size** -- RRset cache should be approximately 2x message cache. Incorrect ratio causes premature cache evictions and query amplification.
2. **Forwarding to upstream with DNSSEC validation** -- When forwarding (not recursing), Unbound cannot fully validate DNSSEC unless the upstream also signs responses. Use `forward-tls-upstream` for trusted forwarding or full recursion for strict DNSSEC.
3. **serve-expired without TTL limit** -- `serve-expired: yes` without `serve-expired-ttl` can return very stale records. Always set a max stale age.
4. **Pi-hole + Unbound port conflict** -- Both default to port 53. Run Unbound on a non-standard port (5335) when co-located with Pi-hole.
5. **qname-minimisation breaking legacy authoritative servers** -- Some old authoritative servers do not handle minimized queries correctly. Use `qname-minimisation-strict: no` (default) to fall back gracefully.
6. **OPNsense custom options overwritten on upgrade** -- Custom unbound.conf edits outside the GUI may be overwritten during OPNsense upgrades. Use the GUI "Custom options" field instead.
## Reference Files
- `references/architecture.md` -- Recursive resolver architecture, DNSSEC, DoT/DoH, modules, pfSense/OPNsense/Pi-hole
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!