Estás en Debian 13. La mitad de tu flota ya no puede acceder a git.internal, pero la otra mitad sí. Alguien “arregló el DNS” ayer y ahora tu portátil resuelve nombres internos en la cafetería, lo cual es gracioso hasta que las consultas se filtran y rompen las suposiciones de zero‑trust.
Este es el clásico fallo de split-horizon DNS: los nombres internos deben resolverse solo en redes internas (o vía VPN), mientras que el DNS público debe permanecer intacto. Lo divertido es que el split-horizon puede fallar en al menos cuatro capas distintas en Debian 13: el servidor autoritativo, el recursor, el resolvedor stub y el enrutamiento por enlace de los servidores DNS. Si aciertas, “lo arreglas” empeorando Internet.
Qué se rompe realmente en el split-horizon en Debian 13
Split-horizon DNS significa que el mismo nombre consultado devuelve respuestas distintas según desde dónde se pregunte. Normalmente eso implica:
- Dentro de la red:
git.internalresuelve a una dirección RFC1918, quizá detrás de un balanceador interno. - Fuera de la red:
git.internalo bien no existe (NXDOMAIN), o resuelve a una dirección pública (raro y arriesgado), o no devuelve nada útil.
En producción real, “split-horizon” no es una única funcionalidad. Es un acuerdo entre:
- DNS autoritativo para tus zonas internas, habitualmente BIND, Knot, PowerDNS, Windows DNS u otra solución gestionada.
- Recursos recursivos a los que los clientes preguntan (Unbound, BIND recursivo, resolutores corporativos, resolutores provistos por la VPN).
- Resolvedor stub del cliente y enrutamiento por política: en Debian 13 eso suele ser
systemd-resolved+ decisiones deresolvectlpor enlace/interfaz. - Dominios de búsqueda y dominios de enrutamiento que deciden si una consulta debe ir al “DNS interno” o al “DNS de internet”.
- Caches por todas partes, incluyendo caché negativa (NXDOMAIN) que puede mantenerte en un estado errado durante minutos u horas incluso después de arreglarlo.
Debian 13 añade una arista peligrosa: la gente asume que el SO sigue comportándose como “editar /etc/resolv.conf, listo.” En un sistema moderno con systemd-resolved, ese archivo puede ser un stub, un enlace simbólico o una mentira que te contaste para dormir tranquilo.
Cuando el split-horizon “sale mal”, las fallas suelen caer en uno de estos grupos:
- Se está usando el resolvedor equivocado (los clientes preguntan al DNS público por nombres internos, o preguntan al DNS interno por nombres públicos).
- Falta el dominio de enrutamiento (los sufijos internos no están ligados a la interfaz VPN, así que las consultas salen por la ruta por defecto).
- Desajuste en los datos autoritativos (las zonas internas y externas divergieron, o una se eliminó).
- Interacción con DNSSEC (los resolutores que validan consideran tu respuesta interna como inválida por supuestos de confianza rotos).
- Caché negativa (los clientes conservan NXDOMAIN incluso después de arreglar la zona).
- Funciones “optimización” como EDNS Client Subnet, DNS64 o caché agresiva se comportan distinto entre resolutores y tus suposiciones explotan.
Una regla operativa: el split-horizon debe ser explícito y comprobable. Si es implícito (“suele funcionar cuando estás en la VPN”), acabará por fallar en una reunión con alguien importante. El DNS tiene talento para el mal timing.
Una cita que sigue vigente en ops: “La esperanza no es una estrategia” — idea parafraseada, frecuentemente atribuida a varios líderes de operaciones.
Broma #1: El DNS es el único sistema donde “está en caché” es a la vez una explicación y una amenaza.
Hechos interesantes y contexto histórico
- El split-horizon es anterior a la nube por décadas. Surgió cuando las empresas publicaban nombres internos y, a la vez, exponían una vista externa curada para socios y la naciente Internet.
- Las “views” de BIND se convirtieron en el mecanismo canónico para servir respuestas diferentes a subredes clientes; ese concepto sigue marcando cómo muchos equipos piensan el DNS dividido.
- La caché negativa está estandarizada. Desde RFC 2308, los resolutores pueden cachear NXDOMAIN según el SOA del zona y su negative TTL; un registro “arreglado” puede parecer roto más tiempo del previsto.
- Los dominios de búsqueda fueron antaño el truco principal. Antes del enrutamiento DNS por enlace, los admins dependían mucho de
searchenresolv.conf. Funcionaba… hasta que los portátiles se encontraron con múltiples redes y VPNs. - La trampa de
.locales histórica, no teórica. mDNS usa.local(RFC 6762). Si lo usaste para DNS unicast interno, probablemente tengas fantasmas en tu resolvedor. - El trabajo de ICANN en 2013 sobre “name collision” mostró cuán frecuentemente las empresas usaban TLDs privados que luego chocaban con el comportamiento público del DNS o nuevos TLDs.
- systemd-resolved introdujo enrutamiento por enlace para que el DNS de la VPN se use solo para ciertos dominios. Resuelve un problema real, pero también significa que el modelo mental antiguo está obsoleto.
- EDNS0 cambió el tamaño de los paquetes. Los resolutores modernos suelen enviar UDP más grande; si PMTU o reglas de firewall son incorrectas, obtienes timeouts raros que parecen “DNS inestable”.
- Algunos resolutores hacen “NXDOMAIN cut” y caché NSEC agresiva. Con DNSSEC, un resolutor puede inferir inexistencia para nombres adyacentes. Si tu historia interna con DNSSEC es confusa, el depurado se vuelve un baile interpretativo.
Guía rápida de diagnóstico (comprobar primero/segundo/tercero)
Primero: confirma qué ruta de resolvedor usa realmente el cliente
- ¿Está funcionando
systemd-resolvedy/etc/resolv.confes un stub? - ¿Qué servidores DNS están configurados por interfaz (VPN vs LAN vs Wi‑Fi)?
- ¿Ves dominios de enrutamiento (~corp.example) aplicados a la interfaz VPN?
Segundo: reproduce con una herramienta, un nombre, un servidor
- Elige un nombre interno único (por ejemplo,
git.internal). - Consulta al resolvedor configurado, luego consulta directamente al servidor autoritativo.
- Compara respuestas, TTLs y si obtienes NXDOMAIN vs SERVFAIL vs timeout.
Tercero: busca interferencia de caché y políticas
- Vacía la caché del cliente y reintenta.
- Revisa el negative TTL en el SOA.
- Verifica el estado de validación DNSSEC si estás validando.
- Inspecciona firewall/MTU si ves timeouts intermitentes.
Cuarto: solo entonces toca la configuración
- Si el cliente consulta al servidor equivocado: arregla el enrutamiento por enlace o la configuración de NetworkManager/VPN.
- Si el resolvedor reenvía mal: arregla el reenvío condicional / zonas stub.
- Si los datos autoritativos están mal: arregla la zona y después gestiona expectativas sobre la demora por caché.
Tareas prácticas (comandos, salidas, lo que significa, qué decides)
Estas son las tareas que realmente ejecuto cuando llega un incidente de split-horizon. Cada una incluye una salida de ejemplo realista y la decisión que tomas a partir de ella. Sustituye nombres/IPs por tu realidad.
Task 1 — Comprobar si systemd-resolved está en la ruta
cr0x@server:~$ systemctl status systemd-resolved --no-pager
● systemd-resolved.service - Network Name Resolution
Loaded: loaded (/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 09:12:18 UTC; 2h 4min ago
Docs: man:systemd-resolved.service(8)
Main PID: 612 (systemd-resolve)
Status: "Processing requests..."
Significado: No estás tratando con “solo resolv.conf”. Hay un resolvedor stub y una caché.
Decisión: Usa resolvectl para inspeccionar DNS por enlace y dominios de enrutamiento; no edites a ciegas /etc/resolv.conf.
Task 2 — Inspeccionar la realidad de /etc/resolv.conf
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Dec 30 09:12 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf
Significado: Las aplicaciones que leen /etc/resolv.conf apuntan al stub local (típicamente 127.0.0.53).
Decisión: Configura DNS vía systemd/network manager, no reescribiendo /etc/resolv.conf (a menos que desactives resolved intencionadamente).
Task 3 — Ver qué servidores DNS y dominios se aplican por enlace
cr0x@server:~$ resolvectl status
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 1.1.1.1
DNS Servers: 1.1.1.1 8.8.8.8
Link 2 (enp1s0)
Current Scopes: DNS
Protocols: +DefaultRoute
DNS Servers: 192.168.10.53
DNS Domain: office.example
Link 5 (tun0)
Current Scopes: DNS
Protocols: -DefaultRoute
DNS Servers: 10.60.0.53
DNS Domain: ~internal.example ~svc.internal.example
Significado: La interfaz VPN tiene dominios de enrutamiento (~internal.example) y no tiene ruta por defecto para DNS. Eso es bueno: solo esos dominios deben ir al DNS de la VPN.
Decisión: Si los nombres internos fallan, comprueba si el nombre consultado coincide con el dominio de enrutamiento. Si es git.internal pero tu dominio de enrutamiento es ~internal.example, has encontrado la discordancia.
Task 4 — Probar un nombre interno usando el stub (lo que ven las apps)
cr0x@server:~$ resolvectl query git.internal.example
git.internal.example: 10.60.12.44 -- link: tun0
-- Information acquired via protocol DNS in 21.4ms.
-- Data is authenticated: no
Significado: El cliente está enrutando esta consulta por la interfaz VPN. Bien. La respuesta existe.
Decisión: Si las aplicaciones siguen fallando, probablemente estés ante caché a nivel de app, nombre equivocado o desajuste TLS/SNI — no un split-horizon DNS.
Task 5 — Cuando falla: distingue NXDOMAIN vs SERVFAIL vs timeout
cr0x@server:~$ resolvectl query git.internal
git.internal: resolve call failed: 'git.internal' not found
Significado: No se encontró en la ruta DNS usada. Esto suele ser NXDOMAIN, pero resolved lo abstrae.
Decisión: Ejecuta dig contra servidores específicos para ver si es realmente NXDOMAIN, o un fallo de política/enrutamiento.
Task 6 — Consultar directamente al resolutor de la VPN
cr0x@server:~$ dig +noall +answer @10.60.0.53 git.internal A
git.internal. 300 IN A 10.60.12.44
Significado: El resolutor interno conoce ese nombre (y el apex de la zona probablemente sea internal. o tienes un TLD privado delegado).
Decisión: Asegúrate de que el cliente realmente rote las consultas de git.internal al resolutor de la VPN. Si tus dominios de enrutamiento son solo ~internal.example, este nombre se filtrará al DNS público y fallará.
Task 7 — Consultar el resolutor público directamente (para ver fuga)
cr0x@server:~$ dig +noall +comments +answer @1.1.1.1 git.internal A
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 29144
Significado: El DNS público devuelve NXDOMAIN. Si los clientes consultan resolutores públicos por git.internal, fallarán y pueden cachear la negativa.
Decisión: Arregla el enrutamiento DNS dividido (dominios de enrutamiento / reenvío condicional). No crees registros públicos para nombres internos como “arreglo rápido.” Así conviertes la arquitectura interna en arquitectura pública.
Task 8 — Revisar dominios de búsqueda y por qué los nombres cortos son una trampa
cr0x@server:~$ resolvectl domain
Link 2 (enp1s0): office.example
Link 5 (tun0): ~internal.example ~svc.internal.example
Significado: Solo office.example es un dominio de búsqueda; los dominios de la VPN son solo de enrutamiento. Una consulta por git puede convertirse en git.office.example, no en git.internal.example.
Decisión: Usa nombres totalmente calificados (FQDNs) para herramientas críticas. Si los humanos deben escribir nombres cortos, define dominios de búsqueda coherentes y acepta el coste operativo.
Task 9 — Vaciar cachés (lado cliente) para eliminar NXDOMAIN obsoletos
cr0x@server:~$ resolvectl flush-caches
cr0x@server:~$ resolvectl statistics
DNSSEC supported by current servers: no
Transactions: 1283
Cache size: 0
Cache hits: 0
Cache misses: 43
Significado: Caché vaciada. Si el problema “se arregla” después de esto, estabas peleando contra un fallo en caché.
Decisión: Revisa el negative TTL en el SOA de tu zona y ajústalo si ves NXDOMAIN de larga duración después de cambios.
Task 10 — Comprobar el SOA de la zona autoritativa para negative TTL (el saboteador silencioso)
cr0x@server:~$ dig +noall +answer @10.60.0.53 internal.example SOA
internal.example. 3600 IN SOA ns1.internal.example. hostmaster.internal.example. 2025123001 3600 600 1209600 3600
Significado: El último campo del SOA (3600) es el negative cache TTL en práctica moderna. NXDOMAIN puede persistir durante una hora.
Decisión: Para zonas con cambios frecuentes, reduce el negative TTL (por ejemplo, 60–300 segundos). No lo pongas a 0 a menos que disfrutes de mayor carga de consultas y nuevos modos de fallo.
Task 11 — Validar que el servidor autoritativo sirve respuestas distintas según la subred cliente (caso BIND views)
cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A
git.internal.example. 300 IN A 10.60.12.44
cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A +subnet=203.0.113.10/32
git.internal.example. 300 IN A 198.51.100.44
Significado: El servidor está realizando comportamiento split-horizon (posiblemente vía views o lógica consciente de ECS). Esa segunda respuesta es una señal de alarma si no es intencional.
Decisión: Si no usas explícitamente EDNS Client Subnet, desactívalo en recursores o asegúrate de que las views se basen en la IP de origen, no en ECS. ECS puede filtrar topología y complicar la caché.
Task 12 — Verificar qué servidor DNS usa la aplicación desde su perspectiva
cr0x@server:~$ getent ahosts git.internal.example
10.60.12.44 STREAM git.internal.example
10.60.12.44 DGRAM
10.60.12.44 RAW
Significado: NSS (glibc) resuelve el nombre. Si dig funciona pero getent falla, probablemente tengas problemas con NSS, DNS en contenedores o distintas librerías de resolución.
Decisión: Trata getent como “lo que ven la mayoría de las apps.” Depura DNS en la capa NSS, no solo con dig.
Task 13 — Confirmar que NetworkManager/VPN entregó DNS correctamente
cr0x@server:~$ nmcli dev show tun0 | sed -n '1,80p'
GENERAL.DEVICE: tun0
GENERAL.TYPE: tun
GENERAL.STATE: 100 (connected)
IP4.DNS[1]: 10.60.0.53
IP4.DOMAIN[1]: ~internal.example
IP4.DOMAIN[2]: ~svc.internal.example
Significado: El perfil VPN está enviando servidor DNS y dominios de enrutamiento. Bien. Si esto falta, resolved no puede tomar decisiones inteligentes.
Decisión: Arregla el perfil VPN (o su plugin) para enviar los DNS y dominios correctos. No lo “arregles” hardcodeando resolutores globales.
Task 14 — Inspeccionar el reenvío en Unbound si ejecutas un recursor local
cr0x@server:~$ sudo unbound-control status
version: 1.19.0
verbosity: 1
threads: 2
modules: 2 [ subnetcache validator ]
uptime: 7261 seconds
options: control(ssl)
Significado: Unbound está activo y podría ser el componente que hace reenvío condicional.
Decisión: Verifica que Unbound tenga explícitos forward-zone o stub-zone para dominios internos; si no, preguntará a Internet público y fallará (o filtrará).
Task 15 — Ver qué proceso está ligado al puerto 53 localmente
cr0x@server:~$ sudo ss -lntup | grep ':53 '
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=15))
Significado: Solo el listener stub está en 127.0.0.53. Si esperabas dnsmasq/unbound escuchando, no lo están.
Decisión: Decide si quieres solo systemd-resolved o un recursor local. Si añades uno, planifica la propiedad del puerto e integración explícitamente.
Caso #33: DNS split-horizon que salió mal
Este caso aparece en tres variaciones. La misma película, distintos actores.
Variación A: La zona interna “funcionaba” hasta que llegaron los portátiles con Debian 13
Durante años, los clientes seguían una regla simple: la Wi‑Fi interna repartía servidores DNS internos via DHCP. Las redes externas repartían resolutores públicos. La gente usaba VPN, pero no importaba mucho porque los nombres internos se necesitaban mayormente on‑prem.
Luego llegó el trabajo remoto. La VPN se volvió por defecto. Alguien habilitó split DNS “correctamente” para que solo los dominios internos fueran al DNS interno, y lo público siguiera público. Gran objetivo.
Pero el esquema de nombres interno era un montón de nombres cortos y un TLD privado (como .internal), mientras que la VPN enviaba dominios de enrutamiento para ~internal.example. Debian 13 no adivinó lo que querías. Enrutó git.internal a resolutores públicos. NXDOMAIN. Cacheado. Outage.
Variación B: El servidor autoritativo estaba bien; la política del cliente era incorrecta
Los admins demostraron que el registro existe consultando directamente el servidor DNS interno. Todos asintieron. Luego “arreglaron” el cliente añadiendo servidores DNS internos como resolutores globales.
Ahora el DNS público se vuelve lento. Peor aún, los resolutores internos empiezan a recibir consultas por dominios públicos que no pueden resolver eficientemente (o no tienen permiso para hacerlo). Alguien añade reenvío para “.” a un resolutor público. Felicidades, construiste una cadena de recursión que filtra consultas internas y añade latencia.
Variación C: La ruta de resolvers era correcta; la caché lo hizo parecer mal
El día anterior al incidente, un cambio en la zona eliminó un registro brevemente. Los resolutores cachearon NXDOMAIN con un negative TTL de 1 hora. El registro volvió 5 minutos después. La gente siguió viendo fallos durante la siguiente hora.
Entonces alguien ejecuta un script masivo de “flush DNS” con sudo. Ayuda. También se convierte en ritual. Así nacen las religiones tribales del DNS.
Tres diseños viables (y cuál elegir)
Diseño 1: Split-horizon autoritativo usando BIND views (clásico)
Cómo funciona: El mismo servidor autoritativo responde a clientes internos desde una view interna y a clientes externos desde una view externa. Los clientes solo preguntan “al servidor DNS” y el servidor elige según la dirección de origen.
Pros: Control central; los clientes permanecen tontos; los dispositivos antiguos funcionan.
Contras: La política basada en IP origen falla cuando los clientes vienen por NAT o resolutores compartidos; más difícil en multi‑cloud; los errores son catastróficos porque la autoridad es la fuente de la verdad.
Elígelo cuando: Tus límites de red son claros, controlas la recursión y puedes identificar redes clientes de forma fiable.
Diseño 2: Una vista autoritativa única, split hecho en la recursión via reenvío condicional (empresa moderna)
Cómo funciona: Las zonas autoritativas están limpias y consistentes. El comportamiento split ocurre en los resolutores recursivos: los resolutores internos saben cómo alcanzar servidores autoritativos internos; los resolutores públicos no. Los clientes usan recursores internos solo cuando corresponde (VPN/on‑prem).
Pros: Arquitectura DNS más limpia; historia DNSSEC más sencilla; evita “dos fuentes de verdad”.
Contras: Requiere enrutamiento cliente correcto (dominios por enlace). Si esa parte falla, las consultas internas se filtran a resolutores públicos y fallan.
Elígelo cuando: Tienes redes mixtas (oficina, casa, VPN) y quieres comportamiento determinista sin depender de IP origen en el autoritativo.
Diseño 3: Split DNS del lado cliente vía dominios de enrutamiento de systemd-resolved (mejor para endpoints Debian 13)
Cómo funciona: El cliente tiene múltiples servidores DNS. Para dominios específicos, las consultas van al DNS de la VPN. Todo lo demás va al DNS público. Esto es exactamente para lo que systemd-resolved es bueno, cuando está configurado correctamente.
Pros: Previene fugas; soporta múltiples redes; explícito; depurable con resolvectl.
Contras: Debes ser disciplinado con el nombrado de dominios. Los TLDs privados y los nombres cortos multiplican el dolor. Algunas apps omiten el stub.
Elígelo cuando: Tienes portátiles, VPN y te importa no romper Internet público en las máquinas cliente.
Si gestionas endpoints Debian 13, Diseño 3 suele ser la respuesta pragmática la mayoría de las veces. Si gestionas servidores en una red estable, el Diseño 2 suele ser más limpio. El Diseño 1 sigue siendo válido, pero es fácil equivocarse a escala.
Configuraciones concretas que arreglan el problema (sin “simplemente hardcodear 8.8.8.8”)
Opción A: Arreglar dominios de enrutamiento por enlace en Debian 13 (systemd-resolved)
La idea clave: haz que los sufijos internos se enruten al DNS de la VPN y mantén el DNS global para el resto.
VPN gestionada por NetworkManager (recomendado en endpoints)
Asegura que tu conexión VPN establezca DNS y dominios de enrutamiento. Puedes hacerlo en el perfil de conexión:
cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns "10.60.0.53 10.60.0.54"
cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns-search "~internal.example ~svc.internal.example"
cr0x@server:~$ nmcli connection up corp-vpn
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/12)
Significado: El ~ marca dominios solo de enrutamiento, no dominios de búsqueda. Eso es lo que quieres: solo las consultas para esos sufijos van al DNS de la VPN.
Decisión: Si dependes de git.internal (sin sufijo), para y renombra o proporciona FQDNs. Los dominios de enrutamiento coinciden con sufijos, no con intuiciones.
systemd-networkd (servidores o instalaciones mínimas)
Para una interfaz VPN, puedes establecer DNS y dominios en unidades .network. Ejemplo:
cr0x@server:~$ sudo sed -n '1,120p' /etc/systemd/network/50-tun0.network
[Match]
Name=tun0
[Network]
DNS=10.60.0.53
DNS=10.60.0.54
Domains=~internal.example ~svc.internal.example
Significado: Esto fija dominios de enrutamiento en la interfaz VPN. El DNS público permanece global.
Decisión: Reinicia la red con cuidado. En sistemas remotos, hazlo en ventana de mantenimiento o con acceso por consola.
Opción B: Reenvío condicional en Unbound (bueno para un resolvedor local “inteligente”)
Si quieres un recursor local que reenvíe zonas internas a DNS internos (y resuelva el resto normalmente), Unbound es sobrio y excelente.
Ejemplo /etc/unbound/unbound.conf.d/forward-internal.conf:
cr0x@server:~$ sudo sed -n '1,120p' /etc/unbound/unbound.conf.d/forward-internal.conf
forward-zone:
name: "internal.example."
forward-addr: 10.60.0.53
forward-addr: 10.60.0.54
forward-zone:
name: "svc.internal.example."
forward-addr: 10.60.0.53
forward-addr: 10.60.0.54
Significado: Las consultas para esas zonas van a resolutores internos; el resto se resuelve vía root hints o tus upstreams configurados.
Decisión: Si haces esto en endpoints, aún prefieres dominios de enrutamiento por enlace para evitar filtrar nombres internos cuando la VPN está desconectada.
Reinicia y verifica:
cr0x@server:~$ sudo systemctl restart unbound
cr0x@server:~$ sudo unbound-control reload
ok
Opción C: Vistas BIND en el autoritativo (útil, pero manejar con cuidado)
Las views son poderosas: te permiten servir archivos de zona distintos a distintos clientes. También son una gran forma de ocultar errores hasta las 2 AM.
Estructura de ejemplo (simplificada) para named.conf:
cr0x@server:~$ sudo sed -n '1,200p' /etc/bind/named.conf.local
acl "internal-nets" { 10.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; };
view "internal" {
match-clients { internal-nets; };
recursion no;
zone "internal.example" {
type master;
file "/etc/bind/zones/db.internal.example.internal";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "internal.example" {
type master;
file "/etc/bind/zones/db.internal.example.external";
};
};
Significado: Clientes desde redes internas ven respuestas internas; el resto ve la zona externa (que quizá esté vacía o devuelva NXDOMAIN según tu configuración).
Decisión: Asegura que el monitoreo consulte ambas views. La mayoría de organizaciones monitoriza solo desde dentro y luego se sorprende cuando socios no resuelven nada.
Opción D: dnsmasq para un sitio pequeño o laboratorio
dnsmasq puede hacer reenvío dividido y caché en un pequeño demonio. Es genial hasta que accidentalmente conviertes tu portátil en servidor DNS para toda la cafetería.
Fragmento de ejemplo:
cr0x@server:~$ sudo sed -n '1,120p' /etc/dnsmasq.d/split-dns.conf
server=/internal.example/10.60.0.53
server=/svc.internal.example/10.60.0.53
no-resolv
server=1.1.1.1
server=8.8.8.8
cache-size=10000
Significado: Las zonas internas se reenvían al DNS interno, todo lo demás a resolutores públicos.
Decisión: Si usas esto, restringe la vinculación a interfaces y reglas de firewall. “Resolver abierto accidental” es una jugada que limita tu carrera.
Broma #2: Ejecutar un resolver abierto es como dejar el coche abierto con un cartel de “viajes gratis”: alguien aceptará la oferta.
Errores comunes (síntoma → causa raíz → arreglo)
1) Los nombres internos fallan solo en VPN
Síntoma: Conectado a VPN; git.internal.example falla, pero sitios públicos funcionan.
Causa raíz: El servidor DNS de la VPN está configurado, pero faltan los dominios de enrutamiento. Las consultas por dominios internos todavía van a resolutores públicos.
Solución: Envía dominios de enrutamiento vía el perfil VPN (~internal.example) y verifica con resolvectl status. No pongas el DNS de la VPN como predeterminado global a menos que lo quieras así.
2) El DNS público se vuelve lento o inestable tras “arreglar” el DNS interno
Síntoma: Tras añadir servidores DNS internos globalmente, la navegación se vuelve lenta; fallos intermitentes al resolver dominios públicos.
Causa raíz: Los resolutores internos no están diseñados/permitidos para recursar hacia Internet, o reenvían a otro resolutor con latencia y filtrado. Insertaste un salto innecesario.
Solución: Usa enrutamiento dividido: zonas internas a DNS internos, todo lo demás a DNS público o a un recursor corporativo adecuado.
3) Funciona con dig, falla en aplicaciones
Síntoma: dig devuelve el registro A correcto, pero curl/git/apt falla al resolver.
Causa raíz: La app usa una ruta de resolución distinta (DNS en contenedor, resolv.conf estático en chroot, NSS mal configurado), o tiene caché de fallo.
Solución: Usa getent ahosts, inspecciona /etc/resolv.conf del contenedor, revisa resolvectl y vacía cachés. Valida que la app no esté fijada a un DNS personalizado.
4) Falla para AAAA pero funciona para A (o viceversa)
Síntoma: IPv4 funciona; resolución IPv6 provoca timeouts o respuestas incorrectas.
Causa raíz: Faltan registros AAAA internamente, supuestos rotos de DNS64/NAT64, o bloqueos de firewall para respuestas DNS más grandes cuando AAAA desencadena conjuntos mayores.
Solución: Decide si soportas IPv6 internamente. Si sí, publica AAAA y asegúrate que MTU/firewall manejen EDNS0. Si no, considera filtrar AAAA en el recursor (último recurso) y sé explícito.
5) SERVFAIL intermitente desde resolutores que validan
Síntoma: Algunos clientes obtienen SERVFAIL; otros están bien; el autoritativo parece responder.
Causa raíz: Falla de validación DNSSEC por firmas inconsistentes, cadena rota o split-horizon que devuelve material DNSSEC diferente al esperado.
Solución: O firma correctamente las zonas internas y gestiona anclas de confianza, o desactiva la validación para esas zonas en el resolutor que valida. Medio‑DNSSEC es peor que no tener DNSSEC.
6) Los nombres internos cortos dejan de funcionar tras migrar a dominios de enrutamiento
Síntoma: Los usuarios escriben git y antes funcionaba; ahora no.
Causa raíz: Los dominios de búsqueda cambiaron o desaparecieron; los dominios de enrutamiento no expanden nombres cortos.
Solución: Pasa a los humanos a FQDNs para servicios críticos. Si debes mantener nombres cortos, añade dominios de búsqueda con conocimiento y prueba colisiones (por ejemplo, git.office.example vs git.internal.example).
7) “Arreglo” provoca fuga de nombres internos a logs públicos DNS
Síntoma: Informes de seguridad muestran nombres internos vistos por resolutores públicos; surgen preocupaciones de privacidad.
Causa raíz: Los clientes consultan resolutores públicos por sufijos internos porque falta o está mal el enrutamiento DNS dividido.
Solución: Impone dominios de enrutamiento y usa recursors internos que rechacen reenviar zonas internas a Internet público. Monitorea fugas consultando resolutores públicos desde clientes controlados y comprobando patrones NXDOMAIN.
Tres mini-historias del mundo corporativo (anónimas, plausibles, técnicamente precisas)
Historia 1: El outage causado por una suposición errónea
Era una empresa mediana con un DNS interno limpio: corp.example para usuarios, svc.corp.example para servicios. El equipo de VPN añadió split DNS y envió ~svc.corp.example a los endpoints. Todo parecía correcto en el ticket.
La suposición errónea fue sutil: creían que todos los servicios internos vivían bajo svc.corp.example. En realidad, un servicio legacy estaba como jira.corp.example, no jira.svc.corp.example. Los usuarios no lo sabían; tenían marcadores.
Al conectarse desde casa, jira.corp.example se fue a DNS público, devolvió NXDOMAIN y se cacheó. Algunos usuarios intentaron más tarde desde la oficina y aun así obtuvieron NXDOMAIN, porque las cachés locales ya estaban “envenenadas” con “no existe”.
Ingeniería pasó la mañana mirando el servidor Jira y su balanceador. Ambos estaban bien. El incident commander finalmente pidió una reproducción única con resolutores explícitos. Ahí quedó clara la discordancia entre dominios de enrutamiento y la convención real de nombres.
La solución fue aburrida: añadir ~corp.example a los dominios de enrutamiento de la VPN y publicar un pequeño “contrato DNS” interno: qué sufijos son internos, cuáles públicos y quién gestiona cambios. El gran cambio fue cultural: no más “los nombres internos son obvios”. No lo son.
Historia 2: La optimización que salió mal
Otra compañía tenía dos recursors internos y un upstream público. Alguien notó picos de latencia DNS y decidió “optimizar” poniendo recursors internos como resolutores globales en todas partes, incluso en portátiles fuera de VPN. Pensaron: los recursors internos tienen mejores caches y pueden reenviar a público igualmente.
Funcionó, hasta que no. Los portátiles en casa empezaron a consultar recursors internos por un túnel VPN que no estaba arriba, o por una ruta que filtraba fragmentos UDP. Las consultas DNS hacían timeout, luego reintentaban por TCP, luego activaban comportamiento de retroceso lento en apps. Las páginas cargaban como en 2003.
El equipo ops añadió dnsmasq local en portátiles para cachear más agresivamente. Eso introdujo otra caché, otro conjunto de timeouts y un nuevo modo de fallo: cuando la VPN subía, dnsmasq no siempre captaba los servidores internos actualizados rápido. Usuarios veían “funciona tras reiniciar”, que es el equivalente IT de “apágalo y enciéndelo”, salvo que ahora es política.
Finalmente lo revertieron y usaron enrutamiento por dominio: sufijos internos a DNS internos solo cuando hay VPN. La latencia mejoró porque quitaron la ruta mal enrutada. La lección fue incómoda: la “optimización” de rendimiento en DNS a menudo solo mueve el tiempo de espera a un lugar menos visible.
Historia 3: La práctica correcta y aburrida que salvó el día
En una firma regulada, los cambios DNS requerían solicitud de cambio con checklist pre y post. Todos se quejaban de lentitud. Entonces ocurrió un incidente de split-horizon durante una fusión: dos redes, dos conjuntos de zonas internas y un puente VPN.
La práctica que salvó no fue tecnología sofisticada. Fue que el equipo DNS tenía un host “canario” fuera de cada red que consultaba continuamente una lista pequeña de nombres internos y externos contra los resolutores previstos. También consultaba desde dentro para asegurar que las views internas permanecían internas. Cada chequeo registraba NXDOMAIN/SERVFAIL y TTL.
Cuando empezó el incidente, no discutieron si el DNS estaba roto. El canario mostró: nombres internos se preguntaban a resolutores públicos desde endpoints conectados a VPN. Eso acotó el problema a dominios de enrutamiento del cliente o configuración de VPN, no a los autoritativos.
El equipo VPN había enviado un perfil nuevo que eliminó dominios de enrutamiento y los reemplazó por dominios de búsqueda. El canario lo detectó en minutos. La reversión fue rápida porque tenían el perfil anterior fijado y probado. Todos volvieron a quejarse de la burocracia, pero la producción siguió viva.
Listas de verificación / plan paso a paso (haz esto, en este orden)
Paso 0 — Decide qué significa “interno”
- Lista los sufijos DNS internos (ejemplo:
internal.example,svc.internal.example). - Prohíbe o elimina gradualmente TLDs privados que no sean subdominios de un dominio que poseas (por ejemplo:
.internal,.corp), a menos que tengas una política deliberada y documentada. - Decide si vas a soportar nombres cortos. Si sí, define el comportamiento de dominios de búsqueda y acepta colisiones.
Paso 1 — Validar los datos autoritativos
- Consulta SOA y NS de cada zona interna desde dentro de la red.
- Confirma TTLs y valores de negative TTL.
- Si el split está en el autoritativo vía views, prueba ambas views desde subredes conocidas.
Paso 2 — Validar comportamiento recursivo
- Asegura que los resolutores internos pueden alcanzar los autoritativos internos de forma confiable.
- Asegura que los resolutores internos no se conviertan en resolutores abiertos accidentales.
- Configura reenvío condicional / zonas stub explícitamente, no por magia no documentada.
Paso 3 — Arregla clientes a la manera Debian 13
- Usa
resolvectl statuspara confirmar servidores DNS por enlace. - Asegura que los enlaces VPN llevan dominios de enrutamiento (
~suffix), no solo dominios de búsqueda. - Mantén DNS global para resolución pública; no enrutes todo por la VPN a menos que la política lo requiera.
Paso 4 — Pon guardarraíles
- Monitoreo desde múltiples puntos de vista (dentro, fuera, VPN) con resolutores explícitos.
- Alerta ante picos súbitos de NXDOMAIN para sufijos internos.
- Runbooks que empiecen con “qué ruta de resolutor se usa”, no con “reinicia bind”.
Paso 5 — Comunica la realidad de la caché
- Informa a las partes interesadas que cambios DNS pueden tardar hasta TTL + negative TTL en propagarse por cachés.
- Tener un procedimiento controlado para vaciar cachés en endpoints (no “reiniciar a todo el mundo”).
- Mantén TTLs sensatos: lo suficientemente bajos para cambios, lo suficientemente altos para evitar tormentas de consultas.
Preguntas frecuentes
1) ¿Por qué empezó a ocurrir esto tras migrar a Debian 13?
Porque la pila de resolvers del lado cliente es más orientada a políticas. systemd-resolved soporta DNS por enlace y dominios de enrutamiento, y no “adivinará” que git.internal pertenece a tu VPN a menos que se lo indiques.
2) ¿Debo desactivar systemd-resolved y volver al /etc/resolv.conf plano?
Sólo si estás dispuesto a reemplazar sus capacidades de enrutamiento por enlace con algo equivalente. Para portátiles y split DNS de VPN, desactivarlo suele empeorar las cosas, no mejorarlas.
3) ¿Cuál es la diferencia entre un dominio de búsqueda y un dominio de enrutamiento (~domain)?
Un dominio de búsqueda expande nombres cortos (la consulta git se convierte en git.office.example). Un dominio de enrutamiento indica a resolved qué servidor DNS usar para nombres dentro de ese sufijo. Los dominios de enrutamiento no expanden nombres cortos.
4) ¿Por qué NXDOMAIN es “pegajoso” incluso después de añadir el registro?
Caché negativa. Los resolutores cachean “no existe” según el negative TTL del SOA de la zona. Vacía cachés para confirmar, luego ajusta el negative TTL si tu ritmo de cambios lo requiere.
5) ¿Está bien usar .internal o .corp como TLD privado?
Funciona hasta que deja de hacerlo. La práctica más segura es usar subdominios de un dominio que controles (por ejemplo internal.example). Los TLDs privados complican el enrutamiento DNS dividido, la detección de fugas y la interoperabilidad.
6) ¿Por qué dig funciona pero mi navegador no?
dig consulta exactamente lo que le indicas. Tu navegador usa el resolvedor del sistema, puede usar DNS-over-HTTPS, o puede estar dentro de un namespace de contenedor. Valida con getent y revisa configuración DoH del navegador y DNS en contenedores.
7) ¿Puedo resolverlo colocando registros internos en DNS público?
Puedes, pero suele ser el intercambio equivocado. Filtras topología interna, complicas el control de acceso y arriesgas exponer servicios sin querer. Arregla dominios de enrutamiento y reenvío condicional en su lugar.
8) ¿Necesito DNSSEC para zonas internas?
No estrictamente, pero necesitas un plan coherente. Resolutores validantes más DNS interno inconsistente es receta para SERVFAIL. O firmas y gestionas anclas correctamente, o desactivas validación para esas zonas en el resolutor.
9) ¿Cuál es la forma más limpia de soportar usuarios on‑prem y VPN?
Usa FQDNs bajo un dominio propio (ej., svc.internal.example), envía dominios de enrutamiento vía VPN y separa DNS público. Luego prueba desde tres puntos: on‑prem, VPN y fuera.
Conclusión: pasos siguientes que no te morderán después
El split-horizon DNS falla cuando dependes de comportamientos implícitos. La pila de resolvers de Debian 13 es explícita: necesita dominios de enrutamiento explícitos, reglas de reenvío explícitas y convenciones de nombres explícitas. Eso es buena noticia, porque significa que puedes hacerlo determinista.
Haz esto a continuación:
- Elige tus sufijos internos y estandarízalos bajo un dominio que controles.
- En endpoints Debian 13, verifica DNS por enlace y dominios de enrutamiento con
resolvectl status. - Arregla perfiles VPN para enviar dominios de enrutamiento tipo
~internal.example, no solo servidores DNS. - Confirma que los SOA autoritativos no tengan negative TTLs que saboteen tu tiempo de recuperación.
- Implementa monitorización desde al menos dos puntos de vista de red y alerta sobre fugas de nombres internos a resolutores públicos.
Y deja de “arreglar el DNS” hardcodeando resolutores globales. Eso no es un arreglo. Es una nueva interrupción con mejor marketing.