Debian 13: conflicto iptables vs nftables — detén la guerra silenciosa del cortafuegos

¿Te fue útil?

Despliegas un host Debian 13 limpio, abres el puerto que necesitas y todo funciona. Luego, una semana después,
llega una actualización inocua de paquete, se actualiza un runtime de contenedores, o alguien “solo añade una regla iptables rápida,”
y de pronto tu cortafuegos se convierte en una casa encantada. El tráfico cae. Los registros mienten. La mitad de los comandos no muestran nada.

Lo que ocurre no es sobrenatural. Son dos front-ends (iptables y nftables) intentando gestionar los mismos
hooks de Netfilter, a menudo mediante capas de compatibilidad, a veces directamente, y ocasionalmente con un tercero
(Docker, Kubernetes, UFW, firewalld) que elige un bando al azar. El resultado es una guerra silenciosa del cortafuegos: las reglas existen,
pero no donde estás mirando.

El problema real: un kernel, múltiples planos de control

En Debian 13, puedes tener:

  • nftables gestionando reglas directamente vía nft y nftables.service
  • iptables comandos que pueden ser:
    • iptables-nft: la CLI de iptables que traduce a reglas nft
    • iptables-legacy: el backend antiguo de iptables que usa interfaces kernel legacy
  • Uno o más gestores de más alto nivel: UFW, firewalld, Docker, Kubernetes, Podman, fail2ban

Ninguno de estos es inherentemente “malo.” El modo de fallo ocurre cuando asumes que todos están mirando la misma
base de reglas. No lo están. A veces miran distintos backends. A veces escriben en el mismo backend pero en tablas/cadenas diferentes con prioridades que no anticipaste.
A veces tus reglas están bien y los paquetes nunca las alcanzan porque offload, bridging, enrutamiento por políticas o el estado de conntrack cambian las reglas del juego.

El pecado operativo es dejar que dos autoridades gestionen la política del cortafuegos. Los cortafuegos son como el control de cambios: solo
necesitas un responsable.

Hechos e historia: por qué llegamos aquí

Algunos hechos concretos te ayudan a depurar más rápido, porque explican por qué “iptables dice X” puede ser insignificante en un sistema moderno.

  1. Netfilter es el marco del kernel; iptables y nftables son front-ends en espacio de usuario. La lucha está mayormente en espacio de usuario, pero el dolor está en la ruta del paquete.
  2. nftables se introdujo para reemplazar iptables. Consolida IPv4/IPv6, soporta sets/maps de forma más natural y reduce la explosión de reglas en casos comunes.
  3. iptables-nft existe para que el tooling heredado siga funcionando. Traduce comandos iptables a reglas nftables, pero el mapeo no siempre es “transparente” cuando inspeccionas.
  4. iptables-legacy todavía puede instalarse y seleccionarse. En un host donde algunos componentes usan legacy y otros usan nft, estás garantizado a confusión y divergencia ocasional de reglas.
  5. UFW históricamente hablaba iptables. Algunos entornos meten UFW en un sistema nftables y asumen que se vuelve “el cortafuegos.” A veces lo es. A veces es un segundo cortafuegos.
  6. firewalld se movió hacia backends nftables. Pero no todas las versiones y no todas las distribuciones lo hacen por defecto igual, y “qué backend está activo” importa.
  7. Docker popularizó la programación automática de iptables. Instalará reglas NAT y filter a menos que se le indique lo contrario, y espera la semántica de iptables incluso cuando el backend es nft.
  8. Kubernetes tradicionalmente asume disponibilidad de iptables. Muchos plugins CNI todavía manipulan reglas iptables; en backends nft suele funcionar—hasta que no.
  9. La prioridad de cadenas en nftables es un concepto de primera clase. En iptables, el orden de cadenas incorporadas está mayormente fijo. En nftables puedes crear múltiples cadenas en el mismo hook con prioridades; eso es poderoso y una buena forma de pegarse un tiro en el pie.

Aquí tienes la cita de fiabilidad que vale la pena mantener en un post-it:

«La esperanza no es una estrategia.» — Gene Kranz

Guía rápida de diagnóstico (haz esto primero)

Cuando los paquetes se están perdiendo o los puertos parecen “abiertos pero inalcanzables”, no empieces editando reglas. Comienza probando quién es el dueño del cortafuegos y dónde se evalúan los paquetes.

1) Identifica el plano de control activo del cortafuegos

  • Comprueba si nftables está cargado y si el servicio está habilitado/activo.
  • Revisa qué backend de iptables está usando tu sistema (nft vs legacy).
  • Verifica si Docker/Kubernetes/UFW/firewalld están instalados y manipulando reglas.

2) Inspecciona las reglas efectivas del kernel, no tus suposiciones

  • Vuelca el ruleset completo de nft.
  • Lista las reglas de iptables con contadores.
  • Busca cadenas duplicadas o en conflicto (especialmente NAT) y políticas por defecto inesperadas.

3) Valida el flujo de paquetes con contadores y capturas dirigidas

  • Usa contadores (iptables -v, nft counter).
  • Usa conntrack para comprobar el estado.
  • Usa tcpdump en entrada y salida para ver dónde muere.

4) Solo entonces cambia la política

  • Elige un gestor y un backend.
  • Elimina o desactiva los otros gestores.
  • Haz la política persistente y prueba el comportamiento tras reiniciarse.

Broma #1: Los cortafuegos son como las cocinas de oficina: todos creen que “solo están ayudando” y luego nada funciona y huele raro.

Un modelo mental práctico: iptables, nftables y el kernel

El kernel tiene una ruta de paquetes con hooks bien definidos: ingress, prerouting, input, forward, output, postrouting (simplificando un poco).
Netfilter provee esos hooks. Los programas en espacio de usuario crean reglas que se enganchan a esos hooks. La parte importante: puede haber múltiples
conjuntos de reglas y múltiples cadenas adjuntas al mismo hook, y se ejecutan en orden.

iptables (clásico) se organiza en tablas (filter, nat, mangle, raw, security) y cadenas incorporadas (INPUT, OUTPUT, FORWARD, PREROUTING, POSTROUTING).
nftables es más flexible: creas tablas y cadenas, luego declaras qué hook y prioridad usa cada cadena.

En Debian 13, la realidad operativa más común es:

  • Tu comando iptables es en realidad iptables-nft por defecto, escribiendo en nftables detrás de escena.
  • nft list ruleset es la verdad fundamental para las reglas en efecto (si estás en backend nft).
  • Si accidentalmente cambias a iptables-legacy para algún componente, puedes tener “dos cortafuegos” diferentes, ambos informando éxito.

El patrón de conflicto se ve así:

  • Ejecutas iptables -S y no ves “nada preocupante”.
  • Ejecutas nft list ruleset y ves una política larguísima que no escribiste.
  • O peor: iptables muestra una política, nft muestra otra, y los paquetes obedecen el backend que esté realmente enganchado.

Tareas prácticas: comandos, salidas, decisiones

Las tareas a continuación están diseñadas para ejecutarse en hosts Debian 13 durante un incidente, una migración o una comprobación de salud.
Cada una incluye: un comando, qué significa una salida típica y la decisión que tomas a continuación. No pases por alto las decisiones; ahí es donde se detienen los incidentes.

Task 1 — Identify which iptables backend is selected

cr0x@server:~$ update-alternatives --display iptables
iptables - auto mode
  link best version is /usr/sbin/iptables-nft
  link currently points to /usr/sbin/iptables-nft
  link iptables is /usr/sbin/iptables
  slave iptables-restore is /usr/sbin/iptables-restore
  slave iptables-save is /usr/sbin/iptables-save
/usr/sbin/iptables-legacy - priority 10
/usr/sbin/iptables-nft - priority 20

Qué significa: Si “currently points to iptables-nft”, entonces los comandos iptables se traducen a nftables.
Si apunta a iptables-legacy, estás usando el backend legacy.

Decisión: Elige un backend en todo el host. Para Debian 13, prefiere iptables-nft a menos que tengas un requisito estricto por legacy.
Los backends mixtos son cómo obtienes reglas fantasma.

Task 2 — Check if nftables service is enabled and active

cr0x@server:~$ systemctl status nftables --no-pager
● nftables.service - nftables
     Loaded: loaded (/lib/systemd/system/nftables.service; enabled; preset: enabled)
     Active: active (exited) since Sun 2025-12-28 10:41:12 UTC; 2h 3min ago
       Docs: man:nft(8)
             man:nftables(8)
    Process: 512 ExecStart=/usr/sbin/nft -f /etc/nftables.conf (code=exited, status=0/SUCCESS)
   Main PID: 512 (code=exited, status=0/SUCCESS)

Qué significa: “active (exited)” es normal; carga las reglas y luego sale. “enabled” significa que las reglas se cargarán al arranque desde /etc/nftables.conf.

Decisión: Si nftables está habilitado, trata /etc/nftables.conf como la fuente de verdad deseada—o desactiva nftables si delegas a otro gestor.

Task 3 — Dump the effective nft ruleset (ground truth)

cr0x@server:~$ nft list ruleset
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif "lo" accept
    tcp dport { 22, 443 } accept
    counter packets 10234 bytes 811234 drop
  }
}

Qué significa: La política efectiva para inet filter input es drop, con reglas explícitas de accept.
El contador en la regla de drop te dice que paquetes están golpeándola.

Decisión: Si tu puerto esperado no está aquí, deja de editar scripts iptables y corrige el ruleset nft o el gestor que lo genera.

Task 4 — See iptables rules with counters (even on nft backend)

cr0x@server:~$ iptables -L -n -v
Chain INPUT (policy DROP 120 packets, 9600 bytes)
 pkts bytes target     prot opt in  out source      destination
 8123  640K ACCEPT     all  --  lo  *   0.0.0.0/0   0.0.0.0/0
  553 42012 ACCEPT     tcp  --  *   *   0.0.0.0/0   0.0.0.0/0   tcp dpt:22
  102  6120 ACCEPT     tcp  --  *   *   0.0.0.0/0   0.0.0.0/0   tcp dpt:443

Qué significa: Si esta salida coincide con el ruleset nft, probablemente estás en iptables-nft y viendo vistas traducidas.
Si no coincide con la salida de nft, puedes tener una situación de backend dividido.

Decisión: Si nft e iptables discrepan, revisa inmediatamente update-alternatives para iptables e ip6tables, y audita quién está instalando reglas.

Task 5 — Verify ip6tables backend too (IPv6 is where surprises hide)

cr0x@server:~$ update-alternatives --display ip6tables
ip6tables - auto mode
  link best version is /usr/sbin/ip6tables-nft
  link currently points to /usr/sbin/ip6tables-nft
  link ip6tables is /usr/sbin/ip6tables
  slave ip6tables-restore is /usr/sbin/ip6tables-restore
  slave ip6tables-save is /usr/sbin/ip6tables-save
/usr/sbin/ip6tables-legacy - priority 10
/usr/sbin/ip6tables-nft - priority 20

Qué significa: Puedes ser nft para IPv4 y legacy para IPv6 si alguien “arregló una cosa”. Eso no es un arreglo; es una bomba de tiempo.

Decisión: Alinea backends IPv4 e IPv6. Si no estás listo para política IPv6, explícitamente descarta o explícitamente permite—no “lo ignores”.

Task 6 — Find who is managing firewall rules (package-level)

cr0x@server:~$ dpkg -l | egrep 'nftables|iptables|ufw|firewalld|fail2ban|docker|kube|podman' | awk '{print $1,$2,$3}'
ii nftables 1.0.9-1
ii iptables 1.8.10-2
ii ufw 0.36.2-2
ii docker.io 26.0.2+dfsg1-1
ii fail2ban 1.1.0-1

Qué significa: El host tiene múltiples posibles escritores: UFW, Docker, fail2ban. Cualquiera de ellos puede añadir o recargar reglas.

Decisión: Elige un “autor de la política.” Si quieres política nativa nftables, configura Docker/fail2ban en consecuencia o constriñe su alcance. De lo contrario acepta que ellos poseen partes del ruleset.

Task 7 — Check if Docker is programming iptables

cr0x@server:~$ grep -R "iptables" /etc/docker/daemon.json 2>/dev/null || echo "no daemon.json iptables setting"
no daemon.json iptables setting

Qué significa: El comportamiento por defecto típicamente es “Docker manipula iptables.” Si eso es aceptable depende de tu modelo.

Decisión: Si necesitas control estricto con nftables, considera configurar Docker "iptables": false e implementar tus propias reglas NAT/forwarding. Si no tienes tiempo para hacerlo de forma segura, no lo hagas a medias.

Task 8 — Inspect nftables for Docker-created tables/chains

cr0x@server:~$ nft list tables
table inet filter
table ip nat
table ip filter

Qué significa: Ver table ip nat y table ip filter es normal, pero si no las creaste, algo lo hizo.
Docker comúnmente inyecta reglas NAT.

Decisión: Si herramientas externas inyectan reglas, documéntalo y monitorízalo. El peor estado es “automatización desconocida.”

Task 9 — Check chain priorities and hooks (nftables ordering issues)

cr0x@server:~$ nft -a list chain inet filter input
table inet filter {
  chain input { # handle 5
    type filter hook input priority 0; policy drop;
    ct state established,related accept # handle 12
    iif "lo" accept # handle 13
    tcp dport 22 accept # handle 14
    counter packets 10234 bytes 811234 drop # handle 20
  }
}

Qué significa: Hook input priority 0. Si tienes múltiples cadenas adjuntas al mismo hook con diferentes prioridades,
necesitas conocer el orden porque un drop en una cadena de mayor prioridad puede impedir que aceptaciones posteriores se ejecuten.

Decisión: Si múltiples cadenas comparten un hook, consolida o documenta prioridades. “Dos cadenas en el hook input” no es automáticamente incorrecto; es automáticamente sospechoso.

Task 10 — Prove packets hit the expected rule (counters)

cr0x@server:~$ nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif "lo" accept
    tcp dport 443 counter packets 0 bytes 0 accept
    counter packets 10234 bytes 811234 drop
  }
}

Qué significa: Si tu contador de accept para 443 permanece en 0 mientras los clientes no conectan, o el tráfico no llega,
está llegando en otra interfaz/stack, o está siendo descartado antes (prioridad, raw table, ingress), o hay un problema de enrutamiento.

Decisión: Pasa a captura de paquetes y comprobaciones de enrutamiento; deja de editar reglas allow a ciegas.

Task 11 — Confirm routing and local listening (not a firewall problem)

cr0x@server:~$ ss -lntp | egrep ':(22|443)\s'
LISTEN 0      4096         0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=710,fd=3))
LISTEN 0      4096         0.0.0.0:443       0.0.0.0:*    users:(("nginx",pid=1188,fd=7))

Qué significa: Los servicios están realmente escuchando. Si esto está vacío, el cortafuegos es inocente y estás cazando al animal equivocado.

Decisión: Si no escucha, arregla el servicio. Si escucha, continúa con comprobaciones de cortafuegos/enrutamiento.

Task 12 — Packet capture: does traffic reach the interface?

cr0x@server:~$ sudo tcpdump -ni eth0 tcp port 443 -c 5
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:55:12.112233 IP 203.0.113.10.52344 > 192.0.2.20.443: Flags [S], seq 123456789, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
10:55:13.112244 IP 203.0.113.10.52344 > 192.0.2.20.443: Flags [S], seq 123456789, win 64240, options [mss 1460,sackOK,TS val 2 ecr 0,nop,wscale 7], length 0

Qué significa: Llegan SYNs. Si no ves SYN-ACKs saliendo, probablemente sea un drop local, enrutamiento local o servicio que no responde.

Decisión: Si SYN llega, revisa contadores nft/iptables y la política otra vez, además de conntrack. Si SYN no llega, estás en terreno de red (ACLs upstream, enrutamiento, security groups, etc.).

Task 13 — Inspect conntrack state for a stuck flow

cr0x@server:~$ sudo conntrack -L | head
tcp      6 431999 ESTABLISHED src=203.0.113.10 dst=192.0.2.20 sport=52344 dport=443 src=192.0.2.20 dst=203.0.113.10 sport=443 dport=52344 [ASSURED] mark=0 use=1

Qué significa: Conntrack ve flujos establecidos. Si ves mucho churn de SYN_SENT o TIME_WAIT,
indica drops, enrutamiento asimétrico o rechazo por la aplicación.

Decisión: Si conntrack muestra establecido pero la app aún falla, sospecha de la aplicación o de problemas MTU. Si conntrack muestra SYNs sin completarse, sospecha del cortafuegos o del upstream.

Task 14 — Check for a policy drop you didn’t mean (nftables default policy)

cr0x@server:~$ nft list chain inet filter input | sed -n '1,60p'
chain input {
  type filter hook input priority 0; policy drop;
  ct state established,related accept
  iif "lo" accept
  tcp dport { 22 } accept
}

Qué significa: Política por defecto drop y no hay accept para tu puerto. Eso no es un bug; es exactamente lo que pediste, o lo que alguien pidió en tu nombre.

Decisión: Añade el puerto en el plano de control correcto. Si un gestor genera este archivo, no lo edites a mano a menos que te gusten las sorpresas al reiniciar.

Task 15 — Detect legacy rules still present (split-brain suspicion)

cr0x@server:~$ iptables-legacy -L -n -v 2>/dev/null | head
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in  out source      destination

Qué significa: Si legacy muestra una política diferente a tu sistema activo basado en nft, probablemente tienes tooling escribiendo a diferentes backends,
o una configuración residual que te morderá durante un failover o reinicio.

Decisión: Si te estás estandarizando en nft, purga o neutraliza el uso legacy (alternatives y servicios). Si debes quedarte en legacy, deja de usar nftables service.

Task 16 — Audit who last touched firewall state (logs)

cr0x@server:~$ journalctl -u nftables -u docker -u ufw --since "today" --no-pager | tail -n 30
Dec 28 09:01:10 server systemd[1]: Starting nftables...
Dec 28 09:01:10 server nft[512]: /etc/nftables.conf:23:3-45: Warning: deprecated syntax
Dec 28 09:01:10 server systemd[1]: Finished nftables.
Dec 28 09:14:22 server dockerd[844]: time="2025-12-28T09:14:22.012345678Z" level=info msg="Loading containers: done."
Dec 28 09:14:22 server dockerd[844]: time="2025-12-28T09:14:22.112345678Z" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.0/16."

Qué significa: nftables se cargó al arranque; Docker se inicializó después y probablemente instaló reglas también. Ese orden puede importar.

Decisión: Si un servicio post-boot sobrescribe o añade reglas al cortafuegos, debes controlar su comportamiento o incorporar sus expectativas en tu política.

Tres minicasos corporativos desde el campo

Mini-story #1: El incidente causado por una suposición equivocada

Una empresa mediana estandarizó Debian para proxies reversos en el edge. Tenían una regla simple: “Todo es nftables ahora.”
Alguien creó una política nft limpia, la puso en /etc/nftables.conf y habilitó el servicio. Pasó staging.
El despliegue fue tan tranquilo que la gente empezó a usar la palabra “aburrido” como cumplido.

Unos meses después, un equipo nuevo añadió un agente de escaneo interno que “requiere iptables.” Instaló un paquete que trajo tooling iptables
y un script auxiliar que ejecutaba iptables -I INPUT -p tcp --dport 9100 -j ACCEPT en el arranque, porque eso era lo que siempre hacía en imágenes antiguas.
Nadie lo pensó dos veces. Vieron la regla en iptables -L. Incluso la documentaron en el runbook.

Luego vino el primer incidente real: algunos nodos rechazaban conexiones al puerto del exporter, pero solo después de reinicios.
Durante la llamada de incidente, la gente repetía: “Pero iptables muestra la regla accept.” Eso era cierto—en esos nodos, iptables había cambiado a legacy debido a un cambio de alternatives.
Mientras tanto nftables había cargado una cadena input por defecto con drop y nunca vio la regla iptables.

La caída no fue dramática. No hubo errores visibles para clientes. Solo métricas faltantes, decisiones erróneas de autoscaling y alarmas de capacidad.
La moraleja fue organizativa: el equipo de plataforma pensó “iptables es una vista legacy de nftables.” El equipo de apps pensó “iptables es el cortafuegos.”
Ambos estaban equivocados, cada uno a su manera.

La solución fue contundente: prohibieron el uso ad-hoc de iptables en esos hosts, fijaron alternatives a iptables-nft y escribieron un método documentado para abrir puertos:
un cambio en el repositorio de nftables, revisado como código. También añadieron una comprobación CI que compara nft list ruleset contra los hooks de cadena y políticas por defecto esperadas.

Mini-story #2: La optimización que salió mal

Otra empresa tenía una plataforma de contenedores muy cargada en hosts Debian. Perseguían latencia, y un ingeniero sugirió “simplificar capas del cortafuegos”
desactivando la programación iptables de Docker y gestionando todo en nftables directamente. En papel se veía limpio: un archivo de política, sin sorpresas,
sin cadenas aleatorias apareciendo.

Cambiaron el ajuste de Docker "iptables": false y lo desplegaron gradualmente. Funcionó en desarrollo. Funcionó en staging.
Entonces producción empezó a reportar errores intermitentes “no se puede alcanzar servicio externo” para algunos contenedores, especialmente después de rotaciones de nodo.
El primer instinto fue DNS. El segundo, enrutamiento. El tercero, culpar a la app.

El problema fue NAT y forwarding. Docker había estado haciendo mucho cableado necesario en silencio: reglas MASQUERADE, reglas FORWARD accept para flujos establecidos,
y algo de comportamiento de aislamiento de bridge. Su política nft hecha a mano cubría el camino feliz pero faltaron algunos casos límite, incluyendo tráfico desde ciertos bridges
y conexiones hairpin. Conntrack empeoró las cosas haciendo que los fallos parecieran inconsistentes, según el estado.

Se recuperaron reactivando la integración iptables de Docker en producción mientras reconstruían la política nft con requisitos explícitos de red para contenedores.
La “optimización” no era en sí incorrecta; el plan de despliegue sí lo fue. Trataron la política de red como un toggle de configuración en vez de un cambio de diseño del sistema.

Lección: si desactivas la automatización de cortafuegos de una herramienta, heredas sus responsabilidades. Eso no es inherentemente malo, pero nunca es gratis.

Mini-story #3: La práctica aburrida pero correcta que salvó el día

Una empresa regulada ejecutaba hosts bastión basados en Debian para acceso administrativo. Las reglas eran estrictas: política por defecto drop, listas explícitas de allow,
y requisito de que cada cambio de cortafuegos sea trazable. Su equipo de seguridad insistió en que la política fuese declarativa, almacenada en un repo y desplegada por automatización.
Todos se quejaron. Luego se acostumbraron.

Un día una tormenta de reinicios de mantenimiento golpeó después de una actualización de kernel. Varios equipos reportaron “SSH caído” en un subconjunto de bastiones.
Olía a problema de cortafuegos, porque todo lo demás estaba sano y la red del hipervisor parecía bien.

El on-call conectó la consola del host, ejecutó nft list ruleset y lo comparó con la última política conocida buena del repo.
Coincidía. Los contadores mostraban SYNs entrantes aceptados. El cortafuegos no era el culpable.
El problema resultó ser un renombrado de interfaz mal ordenado que cambió qué interfaz coincidía con las reglas de allow.

Porque la política era declarativa, versionada e idéntica entre hosts, pudieron descartar la “deriva aleatoria” en minutos.
Eso acortó el incidente y evitó el error clásico: gente añadiendo reglas de emergencia que luego permanecen como agujeros no documentados.

La práctica aburrida no fue el repo. Fue la disciplina de mantener una sola fuente de verdad y probarla al reiniciar.
Así es como evitas que un cortafuegos se convierta en folklore.

Detén la guerra: elige un plano de control y migra limpiamente

Tienes tres estados finales viables en Debian 13. Dos son sensatos. Uno es una trampa.

  • Estado sensato A: política nativa nftables, gestionada por /etc/nftables.conf (o un archivo generado), con nftables service habilitado. iptables usado solo como vista/compat (iptables-nft), no como autor.
  • Estado sensato B: un gestor de más alto nivel (firewalld o UFW) es la única autoridad, y todo lo demás se configura para cooperar. Aún debes saber qué backend usa.
  • La trampa: “Un poco de nftables, más algunas reglas iptables, más Docker que hace lo suyo, más fail2ban que añade un toque.” Eso no es defensa en profundidad. Es ruleta operativa.

Qué recomiendo para la mayoría de servidores Debian 13

Si este es un servidor que operas como infraestructura (VMs, metal bare, edge, bases de datos, balanceadores), prefiere nftables-native.
Es consistente, inspeccionable y scriptable. Luego decide cómo manejar el tooling de contenedores:

  • Si el host ejecuta contenedores y quieres seguridad rápida: permite que Docker gestione sus reglas (con iptables-nft), y trata a Docker como parte del sistema de cortafuegos. Documenta lo que posee.
  • Si el host es sensible en seguridad y necesitas política estricta: desactiva Docker iptables solo si tienes tiempo y experiencia para recrear NAT/forwarding necesario de forma segura.

Cómo “elegir un backend” sin drama

Tu objetivo es: un backend (nft) y un autor (nftables service o un único gestor).
La causa más común de conflicto no es que nftables exista—es que algo sigue escribiendo reglas legacy o las recarga en momentos extraños.

Switch iptables alternatives to nft (if you want nft backend)

cr0x@server:~$ sudo update-alternatives --set iptables /usr/sbin/iptables-nft
update-alternatives: using /usr/sbin/iptables-nft to provide /usr/sbin/iptables (iptables) in manual mode
cr0x@server:~$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
update-alternatives: using /usr/sbin/ip6tables-nft to provide /usr/sbin/ip6tables (ip6tables) in manual mode

Decisión: Después de esto, cualquier tooling basado en iptables debería escribir reglas nft a través de la capa de compat, no legacy.
Si aún ves divergencia, algo está llamando explícitamente a iptables-legacy.

Haz la política nftables persistente (y explícita)

Un archivo nftables mínimo y explícito es mejor que uno ingenioso. Los cortafuegos ingeniosos son cómo la gente termina con reglas “temporales” que duran dos años.
Debian típicamente carga /etc/nftables.conf si nftables service está habilitado.

Además: si migras desde scripts iptables, no intentes traducirlo todo mecánicamente el primer día. Empieza con:
established/related accept, loopback accept, puertos entrantes requeridos y la política de salida necesaria. Luego expande.

Broma #2

Mezclar iptables-legacy y nftables es como ejecutar dos sistemas de nómina: pagarás a todos, solo que no siempre la cantidad correcta.

Errores comunes: síntomas → causa raíz → arreglo

1) “iptables -L parece bien, pero el tráfico sigue bloqueado”

Síntomas: El puerto aparece permitido en la salida de iptables; los clientes siguen en timeout; los contadores nft muestran drops o reglas que no esperabas.

Causa raíz: iptables está usando un backend (a menudo legacy), mientras las reglas efectivas están en nftables (o viceversa). O estás inspeccionando la familia equivocada (ip vs inet).

Arreglo: Revisa alternatives para iptables/ip6tables. Vuelca nft list ruleset. Estandariza en iptables-nft o solo nft. Elimina uso legacy y detén gestores duplicados.

2) “nft list ruleset está vacío después del reinicio”

Síntomas: Las reglas existen justo después de aplicarlas, luego desaparecen tras reiniciar; nftables service inactivo/deshabilitado.

Causa raíz: Cargaste reglas manualmente (interactivo nft) pero no las persististe, o nftables.service está deshabilitado, o un gestor sobrescribió reglas después del arranque.

Arreglo: Habilita nftables service y asegúrate de que /etc/nftables.conf sea correcto. Audita servicios post-boot como Docker/UFW/firewalld por comportamiento de recarga.

3) “La red de Docker falló cuando ‘simplificamos’ el cortafuegos”

Síntomas: Los contenedores no alcanzan redes externas o no aceptan conexiones entrantes; conectividad aleatoria según el ciclo de vida del nodo.

Causa raíz: Desactivaron la integración iptables de Docker sin reemplazar las reglas NAT/forwarding/bridge equivalentes en nftables.

Arreglo: Reactiva iptables de Docker hasta que tengas una política nftables probada para la red de contenedores. Valida el comportamiento de la cadena FORWARD, NAT masquerade y el estado de conntrack.

4) “IPv4 funciona, IPv6 está totalmente abierto (o muerto)”

Síntomas: Escaneos de seguridad muestran exposición IPv6 inesperada; o clientes IPv6 fallan mientras IPv4 funciona.

Causa raíz: Solo configuraste iptables (v4) y olvidaste ip6tables/family inet en nft; o los backends difieren entre IPv4 e IPv6.

Arreglo: Usa nft table inet para política unificada. Alinea alternatives para ambos iptables e ip6tables. Decide una postura explícita para IPv6.

5) “Las reglas existen pero nunca coinciden”

Síntomas: Reglas accept muestran contadores cero; tcpdump ve el tráfico; los clientes fallan igual.

Causa raíz: Desajuste de prioridad/hook de cadena; el tráfico se gestiona en un hook distinto (ingress vs input), o se descarta antes en una cadena de mayor prioridad, o estás filtrando por la interfaz equivocada (renombrados, bonds, VLANs).

Arreglo: Lista cadenas con hooks y prioridades. Consolida cadenas para el mismo hook. Confirma nombres de interfaz y considera emparejar por dirección/subred cuando sea apropiado.

6) “Todo se rompe solo cuando fail2ban arranca”

Síntomas: La protección SSH contra fuerza bruta funciona, pero usuarios legítimos quedan bloqueados; las reglas del cortafuegos crecen mucho con el tiempo.

Causa raíz: fail2ban escribe en iptables mientras gestionas nftables (o viceversa); las baneos persisten de forma inconsistente; la discrepancia de backend provoca baneos obsoletos o ineficaces.

Arreglo: Configura fail2ban para usar acciones nftables cuando nftables sea el plano de control elegido, o aísla fail2ban al mismo backend de forma consistente.

Listas de verificación / plan paso a paso

Checklist A: Estandarizar un host Debian 13 en nftables sin sorpresas

  1. Inventario de escritores: Docker, Kubernetes, UFW, firewalld, fail2ban.
  2. Elige el propietario: nftables nativo (recomendado) o un gestor único.
  3. Alinea alternatives iptables/ip6tables a nft:
    • update-alternatives --set iptables /usr/sbin/iptables-nft
    • update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
  4. Habilita nftables service y asegúrate de que el archivo de configuración sea la única fuente de verdad.
  5. Aplica la política en una ventana de mantenimiento; mantén una consola fuera de banda lista.
  6. Valida: puertos entrantes, conectividad saliente, flujos de contenedores (si aplica), postura IPv6.
  7. Prueba de reinicio. Siempre. Si no haces pruebas de reinicio, estás probando tu suerte.
  8. Monitoriza contadores y registra drops (con moderación) durante unos días.

Checklist B: Si debes mantener tooling estilo iptables

  1. Sigue estandarizando en iptables-nft a menos que tengas un bloqueo probado.
  2. No ejecutes nftables.service con un archivo de política separado que entre en conflicto con reglas gestionadas por iptables.
  3. Asegúrate de que tus comandos de inspección coincidan con tu backend:
    • Backend iptables-nft: inspecciona con ambos iptables -L -v y nft list ruleset.
    • Backend legacy: inspecciona explícitamente con iptables-legacy para evitar autoengaños.
  4. Documenta qué servicio carga reglas al arranque (iptables-persistent, scripts, unidades systemd).
  5. Prueba compatibilidad con Docker/Kubernetes antes del despliegue.

Checklist C: Respuesta a incidentes cuando la propiedad del cortafuegos no está clara

  1. Confirma que el servicio está escuchando (ss -lntp).
  2. Confirma que el tráfico llega (tcpdump en la interfaz).
  3. Vuelca nft ruleset e iptables con contadores.
  4. Revisa alternatives y busca divergencia con legacy rules.
  5. Revisa logs de servicios que recargan reglas después del arranque.
  6. Detén la hemorragia: permite temporalmente solo lo necesario en el backend activo, luego programa una limpieza.
  7. Después del incidente: elimina gestores duplicados y codifica la propiedad de la política.

Preguntas frecuentes (preguntas reales de incidentes reales)

1) ¿Es nftables “mejor” que iptables en Debian 13?

Para la mayoría de uso de servidores, sí: nftables es la interfaz moderna, soporta reglas inet unificadas y es la dirección hacia la que se mueve el ecosistema.
Pero “mejor” no importa si tu tooling espera semántica iptables; entonces la mejor elección es la que puedas operar de forma consistente.

2) ¿Por qué iptables -L muestra reglas que no encuentro en mi archivo nft?

Porque esas reglas pueden ser generadas por otro servicio (Docker, UFW, fail2ban), o pueden ser la vista traducida iptables-nft del estado nftables.
El archivo de configuración no es necesariamente el origen; es solo una posible fuente.

3) ¿Pueden coexistir iptables y nftables de forma segura?

Pueden coexistir si iptables usa el backend nft (iptables-nft) y lo tratas como una interfaz de compatibilidad, no como un segundo autor.
No pueden coexistir de forma segura si mezclas iptables-legacy con política nftables en el mismo host. Eso es split-brain por diseño.

4) ¿Cuál es la forma más rápida de saber si estoy en modo split-brain?

Compara nft list ruleset con iptables -L -v, luego revisa explícitamente iptables-legacy -L.
Si legacy muestra un mundo diferente al de nft, has encontrado tu fantasma.

5) ¿Por qué mis reglas accept tienen contadores cero aunque los clientes estén conectando?

O estás mirando en el lugar equivocado (tabla/familia/hook incorrecto), o el tráfico está siendo aceptado antes en otra cadena,
o nunca llega a ese hook (por ejemplo, tráfico de salida local, reenviado o manejado en ingress).
Los contadores son la verdad, pero solo para la regla que realmente estás coincidiendo.

6) ¿Debo usar table inet o tablas ip/ip6 separadas en nftables?

Usa table inet para la mayoría de la política para que no divergencias accidentalmente el comportamiento IPv4 e IPv6.
Las tablas separadas están bien para casos avanzados, pero aumentan la probabilidad de que IPv6 pase a ser “problema de otro.”

7) ¿Qué pasa con UFW en Debian 13?

UFW puede ser operativo si es la única autoridad y aceptas su modelo.
El problema es ponerlo encima de una política nftables o encima del tooling de contenedores que también escribe reglas.
Elige uno: cortafuegos gestionado por UFW, o por nftables, no ambos.

8) ¿Cómo migro reglas iptables a nftables?

No hagas una traducción 1:1 al principio. Empieza con una política nftables mínima que reproduzca tu intención de seguridad,
luego añade complejidad. Valida con contadores y pruebas de tráfico. Si necesitas mantener tooling iptables temporalmente,
estandariza en iptables-nft y trata la salida nft como el estado canónico.

9) ¿Por qué un reinicio “cambió” el cortafuegos?

Porque un servicio distinto cargó reglas en el arranque, o un gestor aplicó su propia política después de tus reglas,
o los nombres de interfaz cambiaron y tus criterios de match ya no coincidían. El reinicio es el gran suero de la verdad de la configuración.

10) ¿Es buena idea registrar los drops mientras depuro?

Sí, selectivamente. Registra todo y te harás DoS a tu propio disco y a tu propia cabeza. Añade reglas temporales de log con rate limits,
captura lo suficiente para identificar la ruta y luego quítalas. Mantén contadores a largo plazo; son más baratos y a menudo más útiles.

Siguientes pasos que puedes hacer hoy

Si ejecutas Debian 13 en producción y quieres que este problema deje de aparecer a las 3 a.m., haz estas tres cosas:

  1. Decide la propiedad. nftables-native, o un único gestor. No custodia compartida.
  2. Demuestra consistencia de backend. Alinea alternatives iptables e ip6tables; confirma que no estás usando legacy por accidente en un lado.
  3. Operacionaliza la inspección. Añade un conjunto estándar de comandos “verdad del cortafuegos” a tus runbooks:
    nft list ruleset, iptables -L -v, update-alternatives --display iptables, y tcpdump dirigido.

Luego haz la parte aburrida: reinicia un host canario y confirma que las reglas que crees tener son las reglas que realmente obtienes. Ese es todo el juego.

← Anterior
Microcódigo: el «firmware» dentro de tu CPU que ya no puedes ignorar
Siguiente →
VPN de oficina + VoIP: Cómo reducir jitter y latencia en túneles

Deja un comentario