Skip to content
Back to skills

Unbound

ASecurity

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...

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
devopspythonrustgodatabasesecurityperformance

Works with

  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 24, 2026

npx -y skills add chrishuffman5/domain-expert --skill unbound --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Unbound?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Unbound
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/chrishuffman5-unbound/badge)](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.

Download with Pro
SKILL.md
---
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.md9.2 KB
  • references/architecture.md9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…