Unbound es un resolvedor DNS recursivo, validador y con caché, desarrollado por NLnet Labs. En lugar de enviar tus consultas a un resolvedor de terceros, Unbound las resuelve él mismo desde los servidores raíz, valida las firmas DNSSEC y guarda las respuestas en caché. En este tutorial lo instalarás en Ubuntu 24.04 como resolvedor para el propio servidor y para las máquinas de tu red privada, con nombres internos propios y, opcionalmente, reenvío cifrado por DNS-over-TLS.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 512 MB de RAM.
  • Un usuario no root con privilegios sudo.
  • Si otras máquinas van a usar el resolvedor: una red privada entre ellas. En los ejemplos la red es 10.0.0.0/24 y el servidor Unbound tiene la IP privada 10.0.0.5; sustitúyelas por las tuyas.

Paso 1: Instalar Unbound

Instala Unbound y las utilidades dig:

sudo apt update
sudo apt install unbound dnsutils

Comprueba la versión y el estado del servicio:

unbound -V | head -n 1
systemctl status unbound --no-pager
Version 1.19.2
...
     Active: active (running)

Con la configuración por defecto de Ubuntu, Unbound escucha solo en 127.0.0.1 y ::1, por lo que no choca con systemd-resolved, que usa 127.0.0.53. El paquete ya incluye dos ficheros en /etc/unbound/unbound.conf.d/:

  • root-auto-trust-anchor-file.conf: activa la validación DNSSEC con el ancla de confianza de la raíz, que el servicio actualiza automáticamente en cada arranque.
  • remote-control.conf: habilita unbound-control a través de un socket local, sin certificados.

Haz una primera consulta para confirmar que resuelve:

dig @127.0.0.1 cubepath.com +short

Debe devolver una o varias IP.

Paso 2: Configurar el resolvedor

Crea tu configuración en un fichero aparte dentro de unbound.conf.d, así las actualizaciones del paquete no la sobrescriben:

sudo nano /etc/unbound/unbound.conf.d/resolver.conf
server:
    # Direcciones en las que escucha (localhost y la IP privada)
    interface: 127.0.0.1
    interface: ::1
    interface: 10.0.0.5
    # Permite arrancar aunque la IP privada aún no esté configurada
    ip-freebind: yes

    # Quién puede hacer consultas. Todo lo demás se rechaza.
    access-control: 127.0.0.0/8 allow
    access-control: ::1/128 allow
    access-control: 10.0.0.0/24 allow

    # Un hilo por núcleo de CPU
    num-threads: 2

    # Caché: rrset-cache-size debe ser el doble de msg-cache-size
    msg-cache-size: 64m
    rrset-cache-size: 128m

    # Renueva las entradas populares antes de que caduquen
    prefetch: yes
    prefetch-key: yes

    # No revela versión ni identidad a quien pregunte
    hide-identity: yes
    hide-version: yes

    # Protección contra DNS rebinding: una respuesta de Internet
    # no puede apuntar a direcciones privadas
    private-address: 10.0.0.0/8
    private-address: 172.16.0.0/12
    private-address: 192.168.0.0/16
    private-address: 169.254.0.0/16
    private-address: fd00::/8
    private-address: fe80::/10

Ajusta num-threads al número de núcleos (nproc) y el tamaño de la caché a la RAM disponible. Valida la sintaxis y reinicia:

sudo unbound-checkconf
sudo systemctl restart unbound
unbound-checkconf: no errors in /etc/unbound/unbound.conf

Comprueba que Unbound escucha en las tres direcciones:

sudo ss -lnup 'sport = :53'

Deben aparecer 127.0.0.1:53, [::1]:53 y 10.0.0.5:53 asociados al proceso unbound, además de 127.0.0.53 de systemd-resolved.

Paso 3: Permitir las consultas de la red privada

Si UFW está activo, permite el puerto 53 solo desde la red privada:

sudo ufw allow from 10.0.0.0/24 to any port 53
sudo ufw status

Desde otra máquina de la red, consulta al resolvedor:

dig @10.0.0.5 cubepath.com +short

Si obtienes status: REFUSED, la IP del cliente no está cubierta por ninguna línea access-control.

Paso 4: Comprobar la validación DNSSEC

Un dominio firmado correctamente debe devolver el flag ad (authenticated data):

dig @127.0.0.1 isc.org SOA +dnssec | grep flags
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

Y un dominio con la firma rota a propósito debe fallar:

dig @127.0.0.1 dnssec-failed.org | grep status
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 41253

Ese SERVFAIL es el comportamiento correcto: Unbound se niega a entregar una respuesta que no puede validar.

Paso 5: Usar Unbound como resolvedor del servidor

systemd-resolved sigue siendo el resolvedor del sistema y reenvía a los DNS que recibe por DHCP. Para que el propio servidor use Unbound, indícaselo con un fichero adicional:

sudo mkdir -p /etc/systemd/resolved.conf.d
sudo nano /etc/systemd/resolved.conf.d/unbound.conf
[Resolve]
DNS=127.0.0.1
Domains=~.

Domains=~. hace que todas las consultas vayan a ese servidor y no a los DNS de la interfaz. Aplica y verifica:

sudo systemctl restart systemd-resolved
resolvectl status | grep 'DNS Server'
Current DNS Server: 127.0.0.1
       DNS Servers: 127.0.0.1

Repite este mismo fichero en los demás servidores de la red, cambiando 127.0.0.1 por 10.0.0.5.

Paso 6: Añadir nombres internos

Unbound puede responder por nombres que solo existen en tu red, sin montar un servidor autoritativo. Usa el dominio .internal, reservado para este uso. Crea un fichero para ellos:

sudo nano /etc/unbound/unbound.conf.d/local-zones.conf
server:
    local-zone: "internal." static
    local-data: "web.internal. 3600 IN A 10.0.0.10"
    local-data: "db.internal. 3600 IN A 10.0.0.20"
    local-data-ptr: "10.0.0.10 web.internal"
    local-data-ptr: "10.0.0.20 db.internal"

    # Bloquear un dominio concreto
    local-zone: "ads.example.com." always_nxdomain

El tipo static responde con los datos definidos y devuelve NXDOMAIN para cualquier otro nombre bajo internal., sin preguntar a Internet. Aplica y comprueba:

sudo unbound-checkconf
sudo systemctl restart unbound
dig @127.0.0.1 web.internal +short
dig @127.0.0.1 -x 10.0.0.10 +short
10.0.0.10
web.internal.

Paso 7 (opcional): Reenviar por DNS-over-TLS

Por defecto Unbound resuelve de forma recursiva y sus consultas salen sin cifrar hacia los servidores autoritativos. Si prefieres que todo el tráfico salga cifrado hacia un resolvedor de confianza, reenvía por DNS-over-TLS. A cambio, ese proveedor verá tus consultas.

sudo nano /etc/unbound/unbound.conf.d/forward-tls.conf
server:
    tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt

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: 9.9.9.9@853#dns.quad9.net
    forward-addr: 149.112.112.112@853#dns.quad9.net

El nombre tras # es el que Unbound comprueba en el certificado del servidor, y tls-cert-bundle le indica dónde están las CA del sistema. Sin esa línea no verifica el certificado. Aplica y comprueba:

sudo unbound-checkconf
sudo systemctl restart unbound
sudo unbound-control list_forwards
dig @127.0.0.1 cubepath.com +short

list_forwards debe mostrar la zona . con las cuatro direcciones configuradas, y dig debe seguir devolviendo IP.

La validación DNSSEC sigue funcionando con reenvío: repite las pruebas del paso 4.

Paso 8: Consultar estadísticas y vaciar la caché

unbound-control permite ver el estado del resolvedor sin reiniciarlo. Muestra las estadísticas principales sin ponerlas a cero:

sudo unbound-control stats_noreset | grep -E '^total\.num\.(queries|cachehits|cachemiss)='
total.num.queries=1532
total.num.cachehits=1204
total.num.cachemiss=328

Para forzar que un dominio se vuelva a resolver (por ejemplo, tras cambiar sus registros):

sudo unbound-control flush_zone your_domain

Para ver por dónde resolvería Unbound un nombre:

sudo unbound-control lookup cubepath.com

Solución de problemas

  • Unbound no arranca con "can't bind socket: Address already in use": has puesto interface: 0.0.0.0 y choca con systemd-resolved. Usa direcciones concretas como en el paso 2.
  • status: REFUSED desde un cliente: falta su red en access-control.
  • status: SERVFAIL en todos los dominios: suele ser la hora del sistema (DNSSEC depende de ella) o que el servidor no llega a Internet por el puerto 53. Comprueba timedatectl y dig @1.1.1.1 cubepath.com.
  • Depurar una consulta concreta: sube temporalmente el nivel de log con sudo unbound-control verbosity 3, reproduce el fallo, revisa sudo journalctl -u unbound -n 100 y vuelve a sudo unbound-control verbosity 1.

Conclusión

Unbound ya resuelve DNS para el servidor y para tu red privada, valida DNSSEC, responde por los nombres internos de .internal y, si lo activaste, cifra el tráfico de salida con DNS-over-TLS. Como siguientes pasos puedes ofrecer DNS-over-TLS y DNS-over-HTTPS a tus propios clientes con Unbound, o combinarlo con Pi-hole o AdGuard Home como resolvedor de origen para bloquear publicidad sin depender de un proveedor externo.