Back to skills
extension
Category: Development & EngineeringNo API key required

unbound

Unbound recursive DNS resolver — configuration, DNSSEC validation, root hints, performance tuning, Pi-hole integration, and troubleshooting. Triggers on: unbound, recursive resolver, DNSSEC, DNS resolver, root hints, unbound.conf, unbound-control.

personAuthor: jakexiaohubgithub

Identity

  • Unit: unbound.service
  • Config: /etc/unbound/unbound.conf, /etc/unbound/unbound.conf.d/
  • Root hints: /usr/share/dns/root.hints or /var/lib/unbound/root.hints
  • Logs: journalctl -u unbound
  • User: unbound (drops privileges after startup)
  • Install: apt install unbound / dnf install unbound
  • Control: unbound-control (requires key setup: sudo unbound-control-setup)

Key Operations

| Goal | Command | |------|---------| | Check config | sudo unbound-checkconf | | Reload config (no restart) | sudo systemctl reload unbound or sudo unbound-control reload | | Restart service | sudo systemctl restart unbound | | Flush cache | sudo unbound-control flush_zone . | | Test resolution | dig @127.0.0.1 google.com | | Test DNSSEC | dig @127.0.0.1 dnssec.works → should resolve; dig @127.0.0.1 fail.dnssec.works → should SERVFAIL | | View stats | sudo unbound-control stats_noreset | | Update root hints | sudo curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache | | View active config | sudo unbound-control get_option verbosity |

Expected Ports

  • 53/udp+tcp (DNS — listens on configured interfaces)
  • When used with Pi-hole: configure to listen on 127.0.0.1 port 5335
  • Verify: ss -ulnp | grep unbound

Health Checks

  1. systemctl is-active unboundactive
  2. sudo unbound-checkconf → no errors
  3. dig @127.0.0.1 google.com → resolves successfully
  4. dig @127.0.0.1 fail.dnssec.worksSERVFAIL (DNSSEC working)

Common Failures

| Symptom | Likely cause | Check/Fix | |---------|-------------|-----------| | Port 53 conflict | systemd-resolved or another DNS service | ss -ulnp \| grep :53; disable conflicting service | | SERVFAIL for valid domains | DNSSEC validation failure or upstream issue | Test dig @8.8.8.8 domain.com to confirm upstream works; check root hints freshness | | access denied from Pi-hole | access-control not allowing Pi-hole's IP | Add access-control: 127.0.0.1/32 allow (or Pi-hole's IP) | | Slow resolution | Root hints stale, cache cold | Update root hints; warm up cache over time | | Config reload fails | Syntax error | sudo unbound-checkconf to find errors | | DNSSEC failures for specific domains | Domain has broken DNSSEC | Check with dig +dnssec domain.com @8.8.8.8; may need to disable DNSSEC for that zone |

Pain Points

  • Port 5335 convention for Pi-hole stack: When used with Pi-hole, configure unbound to listen on 127.0.0.1@5335 (not 53). Pi-hole listens on 53; unbound handles recursive resolution on 5335.
  • Root hints freshness: Root hints (list of root DNS servers) should be updated periodically (yearly or on major changes). Stale hints still work but may miss new root server IPs.
  • DNSSEC validation is strict: When DNSSEC is enabled, domains with broken DNSSEC will SERVFAIL rather than resolve. This is correct behavior but can surprise users. Use unbound-host to diagnose.
  • access-control required for remote clients: By default, unbound only allows localhost. Explicitly add subnets with access-control: 192.168.1.0/24 allow.
  • interface: directive: By default, listens on localhost only. Set interface: 0.0.0.0 to listen on all interfaces (needed for network-wide resolver).
  • do-not-query-localhost: Set to no when using Pi-hole → unbound stack, so unbound can forward to Pi-hole's blocklist lookup (usually not needed in the simple recursive setup).
  • Cache size tuning: Default cache is small. Increase msg-cache-size and rrset-cache-size (set rrset to 2× msg-cache) for better hit rates on home networks.

References

See references/ for:

  • unbound.conf.annotated — every directive with defaults and recommendations
  • docs.md — official documentation links