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:
dig: couldn't get address for 'google.com': connection refusedRefused query:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 12345Config error:
unbound[1234:0] error: Could not open /etc/unbound/unbound.conf: Permission deniedWhy This Happens
- 1.Access control - Client IP not in allowed range
- 2.Configuration errors - Syntax errors in unbound.conf
- 3.Port binding issues - Cannot bind to port 53
- 4.DNSSEC failures - Validation errors
- 5.Forward zone issues - Upstream servers unreachable
- 6.Permission issues - Cannot read/write files
- 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
| Check | Expected |
|---|---|
| Service running | unbound process active |
| Port listening | UDP/TCP 53 accessible |
| Access control | Client IP allowed |
| Config valid | unbound-checkconf passes |
| Forwarders | Upstream DNS reachable |
| DNSSEC | Trust anchors valid |
| Local zones | Correctly 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 ```
Related Issues
- [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)