What's Actually Happening

Unbound DNS resolver fails to respond to queries. Clients receive timeouts or connection refused errors.

The Error You'll See

```bash $ dig @localhost google.com

;; connection timed out; no servers could be reached ```

Connection refused:

bash
dig: couldn't get address for 'google.com': connection refused

Refused query:

bash
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 12345

Config error:

bash
unbound[1234:0] error: Could not open /etc/unbound/unbound.conf: Permission denied

Why This Happens

  1. 1.Access control - Client IP not in allowed range
  2. 2.Configuration errors - Syntax errors in unbound.conf
  3. 3.Port binding issues - Cannot bind to port 53
  4. 4.DNSSEC failures - Validation errors
  5. 5.Forward zone issues - Upstream servers unreachable
  6. 6.Permission issues - Cannot read/write files
  7. 7.Resource limits - Memory or file descriptor exhaustion

Step 1: Check Unbound Status

```bash # Check unbound service: systemctl status unbound

# Check process: ps aux | grep unbound

# Check listening ports: ss -tlnp | grep :53 ss -ulnp | grep :53

# Check logs: journalctl -u unbound -f

# Check configuration: unbound-checkconf

# Check version: unbound -v

# Check status: unbound-control status

# Check config file: cat /etc/unbound/unbound.conf

# Check additional configs: ls -la /etc/unbound/unbound.conf.d/

# Test locally: dig @127.0.0.1 localhost ```

Step 2: Check Configuration

```bash # Validate configuration: unbound-checkconf

# Check main config: cat /etc/unbound/unbound.conf

# Common configuration: server: interface: 0.0.0.0 port: 53 access-control: 0.0.0.0/0 allow chroot: "" username: unbound directory: /etc/unbound

# Check interface binding: grep interface /etc/unbound/unbound.conf

# Bind to all interfaces: interface: 0.0.0.0 interface: ::0

# Or specific interface: interface: 192.168.1.1

# Check port: port: 53

# Check for syntax errors: unbound-checkconf 2>&1

# Common errors: # 1. Missing colons # 2. Wrong indentation # 3. Invalid option names

# Reload config: systemctl reload unbound

# Or: unbound-control reload ```

Step 3: Check Access Control

```bash # Check access-control settings: grep access-control /etc/unbound/unbound.conf

# Allow all: access-control: 0.0.0.0/0 allow

# Allow specific networks: access-control: 192.168.0.0/16 allow access-control: 10.0.0.0/8 allow access-control: 127.0.0.0/8 allow

# Deny specific: access-control: 192.168.100.0/24 refuse

# Actions: # allow - Allow queries # deny - Drop queries # refuse - Return refused # allow_snoop - Allow with cache snooping

# Test from client: dig @unbound-server google.com

# Check if client IP is allowed: # If client is 192.168.1.100 grep "192.168" /etc/unbound/unbound.conf

# Check for missing allow rule: # Add if needed: access-control: 192.168.1.0/24 allow

# Reload: unbound-control reload

# Check access-control-view for different policies: access-control-view: 192.168.1.0/24 myview ```

Step 4: Check Forwarding Configuration

```bash # Check forward zone: grep -A 10 "forward-zone" /etc/unbound/unbound.conf

# Forward all queries: forward-zone: name: "." forward-addr: 8.8.8.8 forward-addr: 8.8.4.4

# Forward specific domain: forward-zone: name: "example.com" forward-addr: 192.168.1.1

# Check forward addresses: grep forward-addr /etc/unbound/unbound.conf

# Test upstream servers: ping 8.8.8.8 dig @8.8.8.8 google.com

# Test port: nc -zuv 8.8.8.8 53

# Multiple forward servers: forward-addr: 8.8.8.8 forward-addr: 8.8.4.4 forward-addr: 1.1.1.1

# Forward over TLS: forward-addr: 8.8.8.8@853#dns.google

# Check for forward errors: journalctl -u unbound | grep -i forward

# Forward first (try forward, then resolve): forward-first: yes ```

Step 5: Check DNSSEC Configuration

```bash # Check DNSSEC settings: grep -i dnssec /etc/unbound/unbound.conf

# Enable DNSSEC: module-config: "validator iterator"

# Trust anchor: trust-anchor-file: /etc/unbound/root.hints

# Or auto trust anchor: auto-trust-anchor-file: /var/lib/unbound/root.key

# Disable DNSSEC (for testing): # Comment out validator in module-config: module-config: "iterator"

# Check trust anchor: unbound-anchor -v

# Update trust anchor: unbound-anchor -a /var/lib/unbound/root.key

# Check DNSSEC errors: journalctl -u unbound | grep -i dnssec

# Test DNSSEC: dig @localhost google.com +dnssec

# Bogus DNSSEC: # Check for BOGUS status: unbound-control dump_cache | grep bogus

# Flush bogus: unbound-control flush_bogus

# Domain insecurity: domain-insecure: "example.com" ```

Step 6: Check Local Zones

```bash # Check local zones: grep -E "local-zone|local-data" /etc/unbound/unbound.conf

# Local zone for internal domain: local-zone: "local." static

# Local data: local-data: "server1.local. IN A 192.168.1.10" local-data: "server2.local. IN A 192.168.1.11"

# Forward local zone: local-zone: "local." forward

# Transparent local zone: local-zone: "example.com." transparent

# Check local zone types: # static - Authoritative # forward - Forward queries # transparent - Try local, then forward # deny - Deny queries # refuse - Refuse queries

# Check local zone configuration: unbound-control local_zones

# Add local data: unbound-control local_data server1.local. IN A 192.168.1.10

# Remove local data: unbound-control local_data_remove server1.local.

# Test local resolution: dig @localhost server1.local ```

Step 7: Check Permissions and Files

```bash # Check unbound user: id unbound

# Check config permissions: ls -la /etc/unbound/

# Check ownership: chown -R unbound:unbound /etc/unbound/

# Check working directory: ls -la /var/lib/unbound/

# Check log directory: ls -la /var/log/unbound/

# Check PID directory: ls -la /run/unbound/

# Set correct permissions: chown unbound:unbound /var/lib/unbound/ chown unbound:unbound /run/unbound/

# Check chroot: grep chroot /etc/unbound/unbound.conf

# Disable chroot for testing: chroot: ""

# Check root hints: cat /etc/unbound/root.hints

# Update root hints: curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.root

# Check SSL certificates (for DoT): ls -la /etc/unbound/*.pem ```

Step 8: Check Cache and Statistics

```bash # Show statistics: unbound-control stats

# Show stats_nodata: unbound-control stats_nodata

# Check cache: unbound-control dump_cache

# Flush cache: unbound-control flush all

# Flush specific domain: unbound-control flush example.com

# Flush type: unbound-control flush_type example.com A

# Check cache size: unbound-control stats | grep num.query

# Configure cache size: # In unbound.conf: msg-cache-size: 256m rrset-cache-size: 256m

# Check cache hit ratio: unbound-control stats | grep hit

# Prefetch: prefetch: yes

# Check memory usage: unbound-control stats | grep mem ```

Step 9: Debug Query Issues

```bash # Enable verbose logging: # In unbound.conf: verbosity: 2

# Higher verbosity: verbosity: 3 verbosity: 4 verbosity: 5

# Log queries: log-queries: yes log-replies: yes

# Restart: systemctl restart unbound

# Watch logs: journalctl -u unbound -f

# Test with debug: dig @localhost google.com +dnssec +trace

# Check response: dig @localhost google.com +short

# Check authoritative: dig @localhost google.com +nssearch

# Test TCP: dig @localhost google.com +tcp

# Check specific record type: dig @localhost example.com A dig @localhost example.com MX dig @localhost example.com TXT

# Check query stats: unbound-control stats | grep query

# Use unbound-host: unbound-host -v google.com ```

Step 10: Unbound Verification Script

```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-unbound.sh #!/bin/bash

echo "=== Unbound Service ===" systemctl status unbound 2>/dev/null | head -5 || echo "Service not running"

echo "" echo "=== Process ===" ps aux | grep unbound | grep -v grep || echo "No unbound process"

echo "" echo "=== Configuration Test ===" unbound-checkconf 2>&1 | head -10

echo "" echo "=== Listening Ports ===" ss -ulnp 2>/dev/null | grep :53 || netstat -ulnp | grep :53 || echo "Not listening on UDP 53" ss -tlnp 2>/dev/null | grep :53 || netstat -tlnp | grep :53 || echo "Not listening on TCP 53"

echo "" echo "=== Access Control ===" grep access-control /etc/unbound/unbound.conf 2>/dev/null | head -5 || echo "No access-control"

echo "" echo "=== Forward Servers ===" grep forward-addr /etc/unbound/unbound.conf 2>/dev/null | head -5 || echo "No forward servers"

echo "" echo "=== Interface ===" grep "^ interface:" /etc/unbound/unbound.conf 2>/dev/null || echo "Using default interface"

echo "" echo "=== DNSSEC ===" grep -E "module-config|dnssec" /etc/unbound/unbound.conf 2>/dev/null | head -5 || echo "DNSSEC not explicitly configured"

echo "" echo "=== Trust Anchor ===" ls -la /var/lib/unbound/root.key 2>/dev/null || echo "No trust anchor file"

echo "" echo "=== Test Resolution ===" echo "Testing external DNS:" dig @127.0.0.1 google.com +short 2>/dev/null | head -3 || echo "External resolution failed"

echo "" echo "Testing localhost:" dig @127.0.0.1 localhost +short 2>/dev/null || echo "Local resolution failed"

echo "" echo "=== Statistics ===" unbound-control stats 2>/dev/null | grep -E "queries|cache|hit" | head -10 || echo "Cannot get stats"

echo "" echo "=== Firewall ===" iptables -L -n 2>/dev/null | grep 53 || ufw status 2>/dev/null | grep 53 || echo "Check firewall manually"

echo "" echo "=== Recent Logs ===" journalctl -u unbound --no-pager -n 10 2>/dev/null || echo "No logs"

echo "" echo "=== Recommendations ===" echo "1. Verify access-control allows your network" echo "2. Check unbound.conf syntax with unbound-checkconf" echo "3. Ensure interface binding correct" echo "4. Test upstream DNS servers reachable" echo "5. Check DNSSEC trust anchors if enabled" echo "6. Allow UDP/TCP port 53 in firewall" echo "7. Increase verbosity for debugging" EOF

chmod +x /usr/local/bin/check-unbound.sh

# Usage: /usr/local/bin/check-unbound.sh ```

Unbound DNS Response Checklist

CheckExpected
Service runningunbound process active
Port listeningUDP/TCP 53 accessible
Access controlClient IP allowed
Config validunbound-checkconf passes
ForwardersUpstream DNS reachable
DNSSECTrust anchors valid
Local zonesCorrectly configured

Verify the Fix

```bash # After fixing Unbound DNS

# 1. Check service systemctl status unbound // Active running

# 2. Test config unbound-checkconf // No errors

# 3. Test external dig @localhost google.com // Returns A record

# 4. Test local dig @localhost server1.local // Returns local IP

# 5. Check stats unbound-control stats // Shows queries

# 6. Monitor logs journalctl -u unbound -f // No errors ```

  • [Fix BIND DNS Resolution Failed](/articles/fix-bind-dns-resolution-failed)
  • [Fix Dnsmasq DNS Not Resolving](/articles/fix-dnsmasq-dns-not-resolving)
  • [Fix PowerDNS Query Failed](/articles/fix-powerdns-query-failed)