Instalación de Unbound como Resolvedor DNS

Unbound es un resolvedor DNS recursivo de alto rendimiento, seguro y validador de DNSSEC, diseñado para su uso tanto en servidores como en resolvedores de red locales. A diferencia de los resolvedores de proveedores externos como los de Google o Cloudflare, Unbound en tu propia infraestructura garantiza la privacidad de las consultas DNS y elimina la dependencia de terceros. Esta guía cubre la instalación de Unbound en Linux, validación DNSSEC, DNS-over-TLS y optimización de caché.

Requisitos Previos

  • Ubuntu 22.04/Debian 12 o CentOS 9/Rocky Linux 9
  • Mínimo 512 MB de RAM (1 GB recomendado para caché grande)
  • Puerto 53 disponible (deshabilitar systemd-resolved si necesario)
  • Acceso root al servidor

Instalación de Unbound

# Ubuntu/Debian
apt-get update
apt-get install -y unbound unbound-anchor

# CentOS/Rocky Linux
dnf install -y unbound

# Verificar la versión instalada
unbound -V

# Deshabilitar systemd-resolved si ocupa el puerto 53 (Ubuntu)
systemctl disable --now systemd-resolved
rm -f /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf

# Habilitar e iniciar Unbound
systemctl enable --now unbound

# Verificar el estado
systemctl status unbound

Configuración del Resolvedor

El archivo de configuración principal es /etc/unbound/unbound.conf:

cat > /etc/unbound/unbound.conf << 'EOF'
server:
    # Escuchar en todas las interfaces (o especificar una IP)
    interface: 0.0.0.0
    port: 53

    # Activar IPv6 si el servidor lo tiene
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes

    # Número de hilos (uno por CPU disponible)
    num-threads: 4
    
    # Caché distribuida entre hilos
    msg-cache-slabs: 4
    rrset-cache-slabs: 4
    infra-cache-slabs: 4
    key-cache-slabs: 4
    
    # Tamaño de caché (ajustar según RAM disponible)
    msg-cache-size: 64m
    rrset-cache-size: 128m
    
    # Zona de trabajo de los hilos
    so-rcvbuf: 1m
    
    # Prefetch (renovar caché antes de que expire el TTL)
    prefetch: yes
    prefetch-key: yes
    
    # Privacidad: usar puertos UDP aleatorios
    outgoing-range: 8192
    num-queries-per-thread: 4096
    
    # Minimización de consultas (envía menos información a los servidores raíz)
    qname-minimisation: yes
    
    # Seguridad: agregar bit 0x20 para aleatorizar mayúsculas en consultas
    use-caps-for-id: yes
    
    # Duración mínima del TTL en caché (evitar TTLs de 0 que bypassan la caché)
    cache-min-ttl: 60
    cache-max-ttl: 86400
    
    # Logging (minimal en producción)
    verbosity: 1
    log-queries: no
    log-replies: no
    log-tag-queryreply: no
    logfile: "/var/log/unbound/unbound.log"
    
    # Usuario que ejecuta Unbound
    username: "unbound"
    
    # Directorio de trabajo
    directory: "/etc/unbound"
    
    # Archivo de anclaje DNSSEC (actualizado por unbound-anchor)
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    
    # Hardening de seguridad
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-below-nxdomain: yes
    harden-referral-path: yes
    harden-algo-downgrade: no
    
    # Denegar consultas de zonas que no deben ser visibles externamente
    private-address: 192.168.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: 127.0.0.0/8
    private-address: fd00::/8
    private-address: fe80::/10

# Control remoto (para unbound-control)
remote-control:
    control-enable: yes
    control-interface: 127.0.0.1
    control-port: 8953
    server-key-file: "/etc/unbound/unbound_server.key"
    server-cert-file: "/etc/unbound/unbound_server.pem"
    control-key-file: "/etc/unbound/unbound_control.key"
    control-cert-file: "/etc/unbound/unbound_control.pem"
EOF

# Crear directorio de logs
mkdir -p /var/log/unbound
chown unbound:unbound /var/log/unbound

# Generar los certificados para unbound-control
unbound-control-setup

# Verificar la configuración
unbound-checkconf /etc/unbound/unbound.conf

# Reiniciar el servicio
systemctl restart unbound

Validación DNSSEC

Unbound puede validar DNSSEC automáticamente usando el anclaje de confianza de la zona raíz:

# Inicializar/actualizar la clave de anclaje DNSSEC de la zona raíz
unbound-anchor -a /var/lib/unbound/root.key
chown unbound:unbound /var/lib/unbound/root.key

# Verificar que DNSSEC está funcionando
# Una respuesta con "ad" (Authenticated Data) indica validación DNSSEC exitosa
dig +dnssec cloudflare.com @127.0.0.1
# Buscar "flags: qr rd ra ad" en la respuesta

# Probar con un dominio firmado con DNSSEC
dig +dnssec iana.org @127.0.0.1 SOA

# Verificar que Unbound rechaza dominios con firma DNSSEC incorrecta
dig @127.0.0.1 dnssec-failed.org
# Debe devolver SERVFAIL porque tiene una firma DNSSEC rota intencionalmente

# Programar actualización automática del anclaje DNSSEC (cron diario)
echo "0 3 * * * unbound /usr/sbin/unbound-anchor -a /var/lib/unbound/root.key" \
  > /etc/cron.d/unbound-anchor

Control de Acceso

# Añadir ACLs de acceso al archivo de configuración
cat >> /etc/unbound/unbound.conf << 'EOF'

# Control de acceso: quién puede hacer consultas
# Por defecto: denegar todo excepto localhost
server:
    access-control: 0.0.0.0/0 refuse          # Denegar por defecto
    access-control: 127.0.0.0/8 allow          # Permitir localhost
    access-control: 10.0.0.0/8 allow           # Permitir red interna
    access-control: 192.168.0.0/16 allow        # Permitir redes privadas
    access-control: ::1 allow                   # Permitir IPv6 localhost
EOF

# Recargar configuración
unbound-control reload

Optimización de Caché

# Ver estadísticas de la caché en tiempo real
unbound-control stats_noreset

# Estadísticas relevantes:
# total.num.queries              - Consultas totales recibidas
# total.num.cachehits            - Hits en caché (cuanto mayor, mejor)
# total.num.cachemiss            - Misses (consultas recursivas)
# total.num.recursivereplies     - Respuestas recursivas resueltas
# cache.rrset.count              - Entradas actuales en la caché

# Calcular el porcentaje de cache hit
unbound-control stats_noreset | awk '
/total.num.queries/ {queries=$2}
/total.num.cachehits/ {hits=$2}
END {printf "Cache hit ratio: %.1f%%\n", (hits/queries)*100}'

# Limpiar la caché de Unbound
unbound-control flush_zone .  # Limpiar toda la caché
unbound-control flush empresa.com  # Limpiar solo un dominio

# Ajustar el tamaño de caché en tiempo real (sin reiniciar)
# Los cambios en unbound.conf requieren reinicio
# Para cambios en caliente, usar unbound-control set-option

DNS-over-TLS (Reenvío Cifrado)

Configura Unbound para reenviar consultas mediante TLS:

cat >> /etc/unbound/unbound.conf << 'EOF'

# Reenvío de consultas externas mediante DNS-over-TLS
forward-zone:
    name: "."
    
    # Cloudflare DNS-over-TLS
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 1.0.0.1@853#cloudflare-dns.com
    
    # Google DNS-over-TLS (alternativa)
    forward-addr: 8.8.8.8@853#dns.google
    forward-addr: 8.8.4.4@853#dns.google
    
    # Reenvío mediante TLS
    forward-tls-upstream: yes
EOF

unbound-checkconf
systemctl restart unbound

# Verificar que las consultas van cifradas
unbound-control stats | grep "num.query.tls"

Zonas Locales

Define resolución para dominios internos:

cat >> /etc/unbound/unbound.conf << 'EOF'

# Zona local para infraestructura interna
local-zone: "empresa.local." static

# Registros de hosts internos
local-data: "web.empresa.local. 3600 IN A 10.0.1.10"
local-data: "api.empresa.local. 3600 IN A 10.0.2.10"
local-data: "db.empresa.local. 3600 IN A 10.0.3.10"
local-data: "mail.empresa.local. 3600 IN A 10.0.4.10"

# Resolución inversa para la red interna
local-zone: "0.0.10.in-addr.arpa." static
local-data-ptr: "10.0.1.10 web.empresa.local"
local-data-ptr: "10.0.2.10 api.empresa.local"

# Bloquear dominio malicioso (redirigir a IP no válida)
local-zone: "malicioso.com." always_nxdomain
local-zone: "tracking.ad-network.com." always_refuse
EOF

systemctl restart unbound

# Verificar la resolución local
dig @127.0.0.1 web.empresa.local
dig @127.0.0.1 -x 10.0.1.10

Solución de Problemas

# Aumentar verbosidad para depuración (no usar en producción)
unbound-control verbosity 3

# Ver logs en tiempo real
tail -f /var/log/unbound/unbound.log

# Probar resolución con debug
dig @127.0.0.1 google.com +dnssec +multiline

# Verificar que Unbound puede llegar a los servidores raíz
unbound-control lookup google.com

# Ver la caché para un dominio específico
unbound-control dump_cache | grep "google.com"

# Verificar que el puerto 53 está escuchando
ss -ulnp | grep 53

# Comprobar si hay errores de DNSSEC
dig +dnssec @127.0.0.1 dnssec-failed.org
# Debe devolver SERVFAIL

# Restaurar verbosidad normal
unbound-control verbosity 1

Conclusión

Unbound ofrece resolución DNS recursiva con validación DNSSEC completa y privacidad mejorada gracias a la minimización de consultas QNAME, siendo una alternativa sólida y más privada a los resolvedores externos. Su flexibilidad para combinar resolución recursiva directa con reenvío DNS-over-TLS y zonas locales lo convierte en el resolvedor ideal para infraestructuras que requieren control total sobre la resolución DNS sin depender de terceros.