Back to skills
SKILL.md
Unbound
ASecurityExpert 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...
- 4 stars
- 0 votes
- 0 copies
- 0 views
- Added September 24, 2026
Works with
Security analysis
100/100Pro scans all 2 files and shows the line behind each finding
npx -y skills add chrishuffman5/domain-expert --skill unbound --agent claude-codeAre you the author of Unbound?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/chrishuffman5-unbound)---
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
Files in this skill
- SKILL.md
- references/architecture.md
Attribution
Comments
Loading comments…