Redes

DNS soberano en Linux: Pi-hole o AdGuard Home con Unbound como resolver recursivo (sin Cloudflare ni Google)

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30 minutos a 2 horas dependiendo de la complejidad del entorno.
  • Requisitos: Acceso root, Linux moderno, conexión a internet para descargar dependencias.
  • Resultado: Servicio funcional y estable, documentado paso a paso.
  • Troubleshooting: Logs estándar en /var/log/, comandos de verificación al final de cada sección.

Por qué la soberanía DNS importa en 2026

Cuando tu laptop consulta soylinux.com y el resolvedor de tu ISP o un servicio gratuito como Cloudflare 1.1.1.1, Quad9 9.9.9.9 o Google 8.8.8.8 recibe una copia exacta de a qué sitio quieres entrar. Cloudflare ha reiterado públicamente que borra los logs de consultas en 24h, y Quad9 hace lo propio con los PII después de su procesamiento anti-malware. Te puedes creer su palabra, claro. Pero en 2024 Cloudflare tuvo caídas que arrastraron a medio internet, y en 2023 un solo ISP mal configurado envenenó la caché DNS de usuarios de Cloudflare. Si dependés de un tercero para resolver nombres, tu DNS tiene el SLA, la postura de privacidad y los puntos ciegos de ese tercero, no los tuyos.

La alternativa: armar tu propia cadena de soberanía DNS donde cada componente corre en tu infraestructura y nadie externo ve una sola de tus queries. En 2026 el stack canónico para esto es Pi-hole o AdGuard Home + Unbound. El primero bloquea publicidad y trackers en el borde de tu red; el segundo resuelve recursivamente desde los root servers y valida DNSSEC, sin pasar nunca por Cloudflare, Quad9 ni Google. La diferencia respecto al setup clásico (solo Pi-hole + Cloudflare) es que las queries que Pi-hole no tiene en caché ya no salen a internet, sino que se resuelven localmente siguiendo la cadena hasta el servidor autoritativo del dominio.

La cadena de soberanía DNS: cómo funciona

El setup que vas a instalar implementa esta cadena:

[Tu laptop]  -->  [Pi-hole o AdGuard Home]  -->  [Unbound recursive resolver]  -->  [Root servers]
   192.168.1.x         192.168.1.53                    127.0.0.1#5335                  a.root-servers.net
   Puerto 53           Bloqueo + cache                 Recursion + DNSSEC              TLDs -> autoritativos

Cada componente hace su trabajo y nada más. Pi-hole o AdGuard Home decide si el dominio está bloqueado y, si no, lo delega a Unbound. Unbound arranca desde los root hints (la lista de los 13 root servers, embebida en su configuración), pregunta a los TLDs (.com, .net, .org, los ccTLDs), sigue la delegación hasta el servidor autoritativo del dominio, valida la firma DNSSEC si está publicada, y devuelve la respuesta. El cliente nunca toca internet directo, y ningún proveedor externo ve un solo byte de tus queries después del cache hit local.

Esta separación es clave: cuando ves un tutorial con “Pi-hole + Cloudflare DoH”, las queries siguen saliendo a Cloudflare aunque DoH las encripte. Acá no. Lo único que sale a internet es la resolución recursiva a los root servers y a los autoritativos de cada dominio, algo que puedes auditar con unbound-control.

Instalar Unbound en Debian 13/Ubuntu 24.04

El paquete Unbound viene en los repos de Debian y Ubuntu. La versión actual en Debian 13 Trixie es 1.24.x (en Ubuntu 24.04 es 1.19.x pero suficiente para este setup). Si quieres la versión más reciente con los parches de CVE-2026-81642, CVE-2026-81634, CVE-2026-82717 y demás CVEs publicados en 2026, puedes instalar desde el PPA de NLnet Labs o compilar. Para homelabs, el paquete oficial alcanza:

sudo apt update
sudo apt install -y unbound unbound-anchor dnsutils
# Verificar versión
unbound -V | head -3
# Debian 13: unbound 1.24.1
# Ubuntu 24.04: unbound 1.19.3

El paquete unbound-anchor baja y actualiza la trust anchor de DNSSEC automáticamente (la clave de la zona raíz que firma toda la cadena). dnsutils trae dig para validar después. La configuración por defecto de Unbound ya es razonablemente segura, pero vamos a escribir una configuración hardened:

sudo tee /etc/unbound/unbound.conf.d/00-pihole.conf > /dev/null <<'EOF'
server:
    # Solo escuchar en localhost (Pi-hole/AdGuard se conectan acá)
    interface: 127.0.0.1
    interface: ::1
    port: 5335
    # IP fija del Pi-hole/AdGuard si quieres que Unbound vea quién preguntó
    # access-control: 192.168.1.53/32 allow
    # Hardening flags recomendados por NLnetLabs
    harden-glue: yes
    harden-trust-anchor: yes
    harden-below-nxdomain: yes
    harden-referral-path: yes
    harden-algo-downgrade: yes
    # DNSSEC validation
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    # Privacidad: no identificar versión ni hostname en queries
    hide-identity: yes
    hide-version: yes
    # Performance: cache agresivo y prefetch
    msg-cache-size: 64m
    rrset-cache-size: 128m
    cache-min-ttl: 300
    cache-max-ttl: 86400
    prefetch: yes
    # Buffer para queries sostenido (workaround del warning so-rcvbuf)
    so-rcvbuf: 1m
    # Logs estructurados para auditoría
    verbosity: 2
    use-syslog: yes
    log-queries: no
    log-replies: no
    log-local-actions: yes
    # Root hints embebidos (13 root servers)
    root-hints: "/var/lib/unbound/root.hints"
EOF
sudo systemctl restart unbound
sudo systemctl enable unbound
# Verificar que está escuchando en 127.0.0.1:5335
sudo ss -tlnp | grep 5335

El archivo root.hints lo bajás una sola vez y lo mantienes actualizado cada 6-12 meses:

sudo wget -O /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache
sudo chown unbound:unbound /var/lib/unbound/root.hints
sudo systemctl restart unbound

Liberar el puerto 53 antes de configurar los bloqueadores

En Debian 12+, Ubuntu 22.04+ y Fedora, systemd-resolved ocupa el puerto 53 con su stub resolver 127.0.0.53. Si vas a instalar Pi-hole o AdGuard Home de forma nativa, primero liberás el puerto para que no haya conflicto con Unbound (que escucha en 5335) ni con el bloqueador (que escucha en 53):

# Ver quién está en el puerto 53
sudo ss -tlnp | grep :53
# Si es systemd-resolved, desactívalo
sudo systemctl disable --now systemd-resolved
sudo rm -f /etc/resolv.conf
echo "nameserver 9.9.9.9" | sudo tee /etc/resolv.conf
# En Ubuntu con Netplan, también:
sudo sed -i 's/^nameservers:.*/nameservers: [9.9.9.9, 149.112.112.112]/' /etc/netplan/*.yaml 2>/dev/null
sudo netplan apply
# Ahora sí: arrancar Pi-hole o AdGuard Home en puerto 53
sudo systemctl status pihole-FTL    # o adguardhome
sudo ss -tlnp | grep :53            # debe mostrar tu bloqueador

Integrar con Pi-hole como upstream

Con Pi-hole v6 (Core 6.2+, FTL 6.7+), entrá a la web admin http://192.168.1.53/admin/, ve a a Settings > DNS y desmarca todos los Upstream DNS Servers del panel izquierdo. En Custom 1 (IPv4) escribe 127.0.0.1#5335. acepta y bajá la página hasta Settings > DNS > Advanced settings para confirmar que rev-server, cache-size y rate-limit estén razonables. Guardá.

# Verificar desde el server que Pi-hole delega a Unbound
dig @192.168.1.53 soylinux.com +short
# Si responde, ahora verifica que Unbound resuelve directo (sin Cloudflare)
dig @127.0.0.1 -p 5335 soylinux.com +short
# Y que DNSSEC valida la cadena
dig @127.0.0.1 -p 5335 dnssec-failed.org +dnssec  # debe devolver SERVFAIL
unbound-control status | head -10
unbound-control stats | grep -E 'qry|total'

Integrar con AdGuard Home como upstream

AdGuard Home tiene un panel similar. Abrí la web admin en http://192.168.1.53/, ve a a Settings > DNS settings. En Upstream DNS servers reemplazá los defaults por:

# AdGuard Home upstream format: IP[:puerto]
127.0.0.1:5335
# Opcional: agregar Cloudflare o Quad9 como fallback (cualquier cosa que rompa Unbound)
# 9.9.9.9
# [ tls://1.1.1.1 ]

Marcá Use private reverse DNS resolvers (sí, queremos que las PTR también pasen por Unbound si tienes una zona reversa). En DNS cache configuration poné cache size 64 MB. Guardá y aplicá. AdGuard Home reinicia automáticamente y empieza a delegar.

# Validar desde AdGuard Home
dig @127.0.0.1 soylinux.com +short
dig @127.0.0.1 dnssec-failed.org  # debe devolver NXDOMAIN o refused
# Revisar en el dashboard que las queries totales no cayeron a 0
# (si cayeron, es que el upstream está mal configurado)

Validar la soberanía: que tus queries no salgan a Cloudflare

El test clave. Si las queries llegan hasta Unbound y Unbound las resuelve recursivamente, NO hay tráfico hacia Cloudflare, Quad9 ni Google. Lo confirmas así:

Upstream DNS ¿Quién ve las queries? DNSSEC Cache hit local Dependencia externa
Cloudflare 1.1.1.1 Cloudflare (logs borrados en 24h,según política) Validación cliente (browser) No, cache miss Alta
Quad9 9.9.9.9 Quad9 (PII borrados post-procesamiento) Validación cliente No Alta
Google 8.8.8.8 Google (logs persistentes) Validación cliente No Alta
Unbound local Nadie (recursión a root + autoritativos) Validación local con DNSSEC Sí, cache agresivo 64 MB Cero
# 1. Capturar tráfico DNS saliente durante una consulta
sudo tcpdump -i any port 53 -w /tmp/dns.pcap &
sleep 2
dig @127.0.0.1 soylinux.com +short
sleep 1
sudo kill %1
# Convertir captura y mirar los destinos
tshark -r /tmp/dns.pcap -Y 'dns.flags.response==0' -T fields -e dns.qry.name -e ip.dst | sort -u
# 2. Query que sí requiere DNSSEC (zona firmada)
dig @127.0.0.1 -p 5335 dnssec-tools.org +dnssec +short
# Si Unbound no puede validar la cadena, devuelve SERVFAIL
# 3. Estado de Unbound
unbound-control status
unbound-control stats | grep -E 'num.query|query.type|msg.cache.hit'
# 4. Logs en vivo
sudo journalctl -u unbound -f --since '5 min ago'

En el paso 1, los destinos de las queries salientes deben ser los root servers (198.41.0.4, 170.247.170.2, etc.) y los TLDs (192.5.6.30 para .com). Si ves IPs de Cloudflare (1.1.1.1) o Google (8.8.8.8) como destino, algo está mal: o Unbound no está configurado como upstream único, o el access-control está dejando entrar queries externas que reenvía.

Monitoreo en producción

Para homelabs chicos basta con unbound-control stats en un cron cada 5 minutos. Para setups medianos (más de 50 dispositivos, o cuando el servidor Pi-hole/AdGuard corre en una VPS de tu homelab que está en otra red), exportá las métricas a Prometheus y graficá en Grafana:

# 1. Instalar unbound-exporter (en el mismo server o como sidecar)
# Releases: https://github.com/kh@unbound_exporter
sudo apt install prometheus-unbound-exporter   # Debian 13 lo incluye
# O desde el binario:
wget https://github.com/prometheus-community/unbound_exporter/releases/latest/download/unbound_exporter-linux-amd64
chmod +x unbound_exporter-linux-amd64
sudo mv unbound_exporter-linux-amd64 /usr/local/bin/unbound_exporter
# 2. Unit file para systemd
sudo tee /etc/systemd/system/unbound_exporter.service > /dev/null <<'EOF'
[Unit]
Description=Unbound Prometheus exporter
After=unbound.service
[Service]
ExecStart=/usr/local/bin/unbound_exporter --unbound.host=127.0.0.1 --unbound.port=8953
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now unbound_exporter
# Métricas en :9153/metrics
# 3. Prometheus scrape config (en el server Prometheus)
cat >> /etc/prometheus/prometheus.yml <<EOF
  - job_name: 'unbound'
    static_configs:
      - targets: ['192.168.1.53:9153']
EOF
# 4. Métricas clave para alertar:
# - unbound_query_type_total (mix A, AAAA, HTTPS, etc.)
# - unbound_query_flags_total (RD, DO, AD)
# - unbound_cache_hit_ratio (debe estar > 70%)
# - unbound_response_time_seconds (percentil 95)

Troubleshooting real

Warning so-rcvbuf 1048576 was not granted. Got 425984 en journalctl:

sudo sysctl net.core.rmem_max
# Si devuelve 425984, subílo temporalmente
sudo sysctl -w net.core.rmem_max=1048576
# Hacerlo permanente en Debian 13
echo 'net.core.rmem_max=1048576' | sudo tee /etc/sysctl.d/99-unbound.conf
sudo systemctl restart systemd-sysctl
sudo systemctl restart unbound

Latencia alta en la primera query a un dominio nuevo: comportamiento esperado. La primera vez que visitás un dominio, Unbound arranca desde los root servers, baja la cadena hasta el autoritativo y la respuesta llega en 500ms-2s. Después queda en msg-cache y responde en menos de 5ms. Si tienes prefetch: yes activado (en mi config arriba), Unbound renueva las entradas antes de que expiren, así que las queries calientes siempre son rápidas.

DNSSEC fails raros en sitios que antes funcionaban: si activaste DNSSEC y un sitio devuelve SERVFAIL de repente, casi siempre es que ese dominio publicó un DS record incorrecto o una firma expirada. puedes deshabilitar DNSSEC temporalmente solo para ese dominio con local-zone: "ejemplo.com" transparent en /etc/unbound/unbound.conf.d/, pero reportá el bug al dueño del sitio. Si afecta a muchos dominios, es probable que el root.key esté vencido: regeneralo con sudo unbound-anchor -a /var/lib/unbound/root.key.

Las queries vuelven a salir a Cloudflare a pesar de todo: casi siempre es que olvidaste desmarcar los upstreams default en Pi-hole o quedaron en AdGuard Home. verifica:

# Desde el server Pi-hole/AdGuard
pihole -q -exact 1.1.1.1       # Pi-hole: ver si Cloudflare está como upstream
# Para AdGuard: revisar la config YAML
cat /opt/AdGuardHome/AdGuardHome.yaml | grep -A2 upstream_dns
# La lista debe contener SOLO 127.0.0.1:5335 (o el fallback que hayas decidido)

¿Cuándo SÍ y cuándo NO?

Vale la pena armar soberanía DNS completa cuando: gestionas un homelab con más de 10 dispositivos, te importa la privacidad real de las queries, quieres evitar depender de un tercero que ya tuvo outages, necesitas DNSSEC validado de extremo a extremo, o quieres auditar exactamente qué hace cada query que sale de tu red. Para periodistas, abogados, investigadores y cualquiera que maneje datos sensibles, es la línea base mínima.

NO vale la pena cuando: tienes un único dispositivo y ya usas Firefox/Brave con DoH a un proveedor confiable, vives en una red corporativa donde el IT ya gestiona DNS centralmente, o tu motivación es solo bloquear publicidad (ahí Pi-hole o AdGuard Home solos con Cloudflare/Quad9 como upstream resuelven el 95% del caso). Si quieres el balance entre soberanía y simplicidad, deja Unbound configurado pero mantené Cloudflare o Quad9 como fallback opcional: Unbound intenta recursión primero y si falla, sale al upstream.

Trade-off real: la soberanía cuesta latencia en cold cache y un poco de sysadmin extra. Si tu red tiene 100+ dispositivos haciendo miles de queries por minuto, Unbound las maneja sin despeinarse. Si tienes 3 dispositivos y consultás 50 sitios por día, casi no notas la diferencia. La decisión es cuánto vale para ti que esas 50 queries no lleguen a un resolver externo.

Resumen

Unbound + Pi-hole o AdGuard Home es el stack canónico de soberanía DNS en 2026. La instalación toma una hora, el mantenimiento es de unos minutos cada 6 meses (rotar root hints y root.key), y la recompensa es que ninguna query de tu red sale a Cloudflare, Quad9 o Google a menos que tú explícitamente lo permitas como fallback. Si ya tienes Pi-hole o AdGuard Home instalado siguiendo nuestra guía de instalación base, agregar Unbound es un paso natural: pasás de “bloqueador con upstream comercial” a “resolver recursivo validado con DNSSEC”. La diferencia, en una sola línea, es pasar de confiar en el DNS de alguien más a confiar en tu propio DNS.

Preguntas frecuentes

¿Por qué DNS soberano en Linux: Pi-hole o AdGuard Home con Unbound como resolver recursivo (sin Cloudflare ni Google) es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con DNS soberano en Linux: Pi-hole?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con DNS soberano en Linux: Pi-hole?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar DNS soberano en Linux: Pi-hole y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a DNS soberano en Linux: Pi-hole?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *