Aplicas un cambio en nftables, ejecutas nft list ruleset y lo ves, el tráfico se comporta durante cinco minutos… luego vuelve la realidad. Después de un reinicio es peor: tu «permitir SSH» desaparece, Docker hace malabares con tu NAT y la dirección pregunta por qué el “ajuste simple del firewall” se convirtió en un safari de interesados.
Esto normalmente no es «nftables siendo inestable». Casi siempre es el orden de carga, gestores en conflicto, o un ruleset que está técnicamente cargado pero funcionalmente evitado. Vamos a arreglarlo de forma que permanezca arreglado.
Qué significa realmente “las reglas no funcionan”
Cuando alguien dice “nftables no funciona”, normalmente se corresponde con uno de estos modos reales de fallo:
- Ruleset no cargado en absoluto tras el arranque o después de reiniciar un servicio. Estás viendo un ruleset vacío o un marcador por defecto.
- Ruleset cargado, pero sobrescrito más tarde por otro actor (Docker, firewalld, un agente de nube, una unidad de iptables-restore, tu propio trabajo de CI).
- Ruleset cargado, pero no coincide porque está en el hook equivocado, en la familia equivocada (ip vs inet), tipo de cadena incorrecto o prioridad incorrecta.
- Las reglas coinciden, pero tu expectativa es incorrecta (estado conntrack, ruta NAT, política de enrutamiento, bridge vs ruta enrutada, tráfico local vs reenviado).
- El camino del kernel te evita debido a offloads, programas XDP, supuestos de VRF/policy routing, o un netns distinto del que crees que estás filtrando.
El truco es dejar de discutir con tu modelo mental y interrogar el sistema en ejecución. Debian 13 es una plataforma limpia para nftables—hasta que accidentalmente ejecutas dos orquestadores de firewall al mismo tiempo, o confías en “se cargó una vez en una sesión de shell, así que debe persistir”.
Chiste #1: Los firewalls son como las reuniones—si no controlas a quién invitas, alguien reescribirá la agenda justo después de que te vayas.
Hechos y contexto que conviene conocer (para que dejes de adivinar)
- nftables reemplazó a iptables como interfaz a netfilter en Linux hace años; iptables puede ser un wrapper de compatibilidad sobre nft (iptables-nft) o el backend legacy (iptables-legacy).
- Debian históricamente soportó ambos backends porque las flotas de producción no migran en un fin de semana. Eso significa que puedes tener una vista «funcionando» en iptables que no refleja lo que nft ve.
- La familia
inetes un regalo práctico: un ruleset puede cubrir IPv4 y IPv6. La trampa es que algunos ejemplos antiguos aún usanipyip6por separado, y mezclarlos sin intención puede crear huecos. - Las prioridades de cadena importan porque varias cadenas base pueden engancharse al mismo hook (input/forward/output/prerouting/postrouting) y ejecutarse por orden de prioridad. “Mi regla existe” no implica “mi regla se ejecuta primero”.
- Docker históricamente programa reglas de firewall/NAT automáticamente y puede hacerlo a través de interfaces iptables. Con iptables-nft, eso sigue terminando en nftables—pero no en el sitio que esperas.
- firewalld no es “solo una interfaz gráfica”; es un gestor con estado que reasegurará sus reglas deseadas. Si editas nft a mano en un host gestionado por firewalld, tarde o temprano discrepará contigo y ganará.
- Los rulesets de nftables son atómicos cuando se cargan desde un archivo: los errores de parseo suelen resultar en “nada cambiado”, lo cual es bueno—a menos que estés mirando el archivo equivocado y pienses que se cargó.
- Netfilter es por espacio de nombres de red. Los contenedores y algunos gestores de servicio pueden ejecutarse en su propio netns. Podrías estar cargando reglas en el namespace raíz mientras el tráfico que te importa vive en otro.
- El arranque basado en systemd de Debian es paralelo. El orden es explícito, no mágico. Si tu firewall depende de interfaces, módulos o sysctls, debes codificar ese orden o tendrás “funciona en mi reinicio”.
Guía rápida de diagnóstico
Esta es la secuencia “detener la hemorragia” que uso cuando alguien avisa “el cambio de firewall no se aplicó” y el reloj suena fuerte.
Primero: demuestra qué reglas están realmente activas
- Ejecuta
nft list rulesety guárdalo. Si está vacío o falta tus tablas, estás depurando lo incorrecto. - Comprueba si estás viendo el namespace raíz. Si tienes contenedores o namespaces de red, verifica dónde vive el tráfico.
- Confirma qué framework está a cargo: servicio nftables, firewalld, Docker, una unidad de iptables-restore o un agente de gestión de configuración.
Segundo: encuentra quién escribió las reglas por última vez
- Usa los journals de systemd: busca recargas de nftables, inicios de firewalld, reinicios de docker y unidades iptables-restore.
- Revisa las marcas de tiempo en tus archivos de ruleset. Si son correctas pero las reglas en vivo difieren, alguien las sobrescribió después de la carga.
Tercero: valida la ruta del paquete y hook/prioridad
- ¿Es
inputoforward? El tráfico local frente al enrutado confunde a todo el mundo una vez. - ¿El NAT ocurre antes de lo que asumes? ¿Coincides por direcciones originales o traducidas?
- ¿Hay múltiples cadenas base en el mismo hook? Inspecciona prioridades y políticas.
Cuarto: decide una única fuente de la verdad
- O bien: nftables puro con un único archivo de ruleset y una unidad systemd.
- O bien: firewalld como gestor.
- O bien: un orquestador superior (CNI de Kubernetes, etc.).
Mezclarlos es cómo obtienes “las reglas no funcionan”, salvo que las reglas están funcionando—solo que no son las tuyas.
Orden de carga: systemd, red y por qué el tiempo importa
En Debian 13, el caso más común de “funcionó manualmente pero no en el arranque” es una carrera simple: el servicio nftables se carga antes del entorno que supone que existe.
¿Qué significa “entorno” aquí?
- Interfaces: si tus reglas se refieren a
iifname "ens192"pero la interfaz es renombrada por udev más tarde, puedes tener una descoincidencia en el arranque. - Módulos del kernel: algunos matches/conntrack/NAT dependen de módulos disponibles. Normalmente se cargan automáticamente, a veces no, especialmente con protocolos inusuales.
- Sysctls: forwarding, rp_filter, bridge-nf-call-iptables, etc. Estos pueden cambiar qué tráfico llega siquiera a los hooks de netfilter.
- Gestores de red: ifupdown, systemd-networkd, NetworkManager—cada uno tiene su propio timing y puntos de hook.
El orden de systemd es explícito. Si no lo especificas, obtienes “mejor esfuerzo”. En producción, el mejor esfuerzo es como te vuelves canoso.
Guía con opinión
- Mantén nftables lo más temprano posible para políticas default-drop en input, pero no escribas reglas específicas de interfaz que dependan de renombres tardíos.
- Si debes depender de interfaces, ordena nftables después de que la red esté arriba (o al menos después de que udev haya terminado) y acepta que el tráfico de arranque temprano esté menos controlado.
- Prefiere hacer coincidir direcciones/subredes sobre nombres de interfaz cuando sea posible, especialmente en hosts con direccionamiento predecible.
Conflictos: iptables, firewalld, Docker y compañía
Los conflictos no son filosóficos. Son literalmente múltiples procesos escribiendo en el mismo ruleset de netfilter, a veces a través de APIs distintas, con suposiciones y comportamientos de recarga diferentes.
iptables vs nftables: el espejo con dos caras
En Debian, iptables puede operar en dos modos:
- iptables-nft: los comandos iptables programan reglas de nftables detrás de escena.
- iptables-legacy: los comandos iptables programan el backend legacy xtables, separado de nftables.
Si no sabes cuál está activo, puedes “arreglar” el firewall usando la herramienta equivocada y luego preguntarte por qué nftables no cambió. O al revés: nftables se ve bien, pero iptables-legacy es el que realmente filtra.
firewalld: el dictador educado
El trabajo de firewalld es mantener un estado deseado. Si editas reglas nft directamente en un sistema gestionado por firewalld, estás pintando un graffiti en una pizarra blanca que se borra en la siguiente actualización.
Docker: útil hasta que no lo es
Docker suele asegurar la conectividad de contenedores inyectando reglas NAT y filter. Dependiendo de configuraciones y versiones, puede:
- crear sus propias cadenas y hacer jumps hacia ellas
- cambiar defaults de forwarding
- reaplicar reglas al reiniciar el daemon (lo que puede ocurrir durante upgrades, rotación de logs o presión de recursos)
Si ejecutas políticas estrictas de nftables en un host Docker, debes planear la programación de reglas por parte de Docker. El enfoque correcto suele ser integrarse con él (permitir sus cadenas pero limitarlas), no fingir que no existe.
Chiste #2: Docker y los firewalls tienen un estado de relación: “Es complicado”, y tu ventana de cambio es el terapeuta de pareja.
Hooks, prioridad de cadenas y la ilusión de “mi regla está”
nftables no es “una gran lista”. Son tablas, cadenas, hooks y prioridades. Si vienes de la memoria muscular de iptables, la estructura puede sentirse como si alguien reorganizara tu caja de herramientas en cajones etiquetados. Eso es bueno—hasta que pones la llave inglesa en el cajón de “cucharas” y te preguntas por qué nada aprieta.
Tres problemas que parecen reglas rotas
- Hook equivocado: bloqueas en
inputpero el tráfico se está reenviando a través del host, así que golpeaforwarden su lugar. - Familia equivocada: escribiste
table ip filterpero el tráfico es IPv6, así que queda intacto.table inet filtersuele ser el predeterminado pragmático. - Inversión de prioridad: una cadena base con mejor prioridad (numéricamente menor) se ejecuta antes que la tuya y acepta el tráfico, por lo que tu DROP posterior nunca se activa.
Prioridad de cadenas en la práctica
Las prioridades son enteros. Los menores se ejecutan antes. NAT tiene prioridades convencionales (por ejemplo, dstnat en prerouting, srcnat en postrouting), pero aún puedes crear cadenas base en competencia. Si un agente proveedor instala una cadena base en el mismo hook con prioridad anterior, tu política cuidadosa puede volverse decorativa.
Al diagnosticar, no solo leas tu propio archivo de configuración. Inspecciona el ruleset en vivo e identifica todas las cadenas base adjuntas a cada hook. Luego razona sobre el orden.
Una cita para mantener en tu monitor
parafraseando una idea
de John Allspaw: la confiabilidad es una propiedad del sistema, no de componentes individuales comportándose según lo diseñado.
Tareas prácticas: comandos, salidas, decisiones (la lista de verificación para producción)
Querías comandos. Aquí están los comandos. Cada tarea incluye qué significa la salida y qué decisión tomar después.
Task 1: Confirmar estado del servicio nftables y última acción
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 09:11:08 UTC; 2h 3min ago
Docs: man:nft(8)
Process: 412 ExecStart=/usr/sbin/nft -f /etc/nftables.conf (code=exited, status=0/SUCCESS)
Main PID: 412 (code=exited, status=0/SUCCESS)
Significado: El servicio se ejecutó y salió correctamente (normal en oneshot). Si está inactivo/fallido, tus reglas pueden no cargarse.
Decisión: Si falló, inspecciona los logs y ejecuta una carga manual en modo check (Task 4). Si está activo, continúa: algo más puede estar sobrescribiendo las reglas.
Task 2: Inspeccionar el journal por recargas y servicios en competencia
cr0x@server:~$ journalctl -u nftables -u firewalld -u docker --since "today" --no-pager
Dec 28 09:11:08 server systemd[1]: Starting nftables...
Dec 28 09:11:08 server nft[412]: /etc/nftables.conf:52:16-16: Error: Could not process rule: No such file or directory
Dec 28 09:11:08 server systemd[1]: nftables.service: Main process exited, code=exited, status=1/FAILURE
Dec 28 09:11:08 server systemd[1]: Failed to start nftables.
Dec 28 09:19:43 server systemd[1]: Starting Docker Application Container Engine...
Dec 28 09:19:44 server dockerd[733]: time="2025-12-28T09:19:44.101" level=info msg="Loading containers: done."
Significado: Aquí nftables falló debido a un error de configuración (un include faltante es común). Docker arrancó más tarde y programará reglas de todos modos.
Decisión: Arregla el error de parseo de nftables antes de perseguir “conflictos”. Si nft falla en el arranque, estás peleando con una política no cargada.
Task 3: Verificar qué backend de iptables está seleccionado
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
Significado: Los comandos iptables manipularán nftables. Eso es bueno para un ruleset unificado, pero también significa que Docker/otros usuarios de iptables están escribiendo en nft.
Decisión: Si ves legacy seleccionado en un host que gestionas con nftables, detente y alinea las herramientas. Mezclar backends = teatro de depuración.
Task 4: Validar tu config sin aplicarla
cr0x@server:~$ nft -c -f /etc/nftables.conf
/etc/nftables.conf:12:1-14: Warning: table ip filter is managed by iptables-nft, do not touch!
Significado: La config parsea, pero estás escribiendo en una tabla que otra herramienta (iptables-nft) también gestiona. El texto de la advertencia varía, pero la situación es real.
Decisión: No luches por el mismo nombre de tablas. Usa tus propias tablas/cadenas e intégrate con jumps si es necesario, o deja de usar herramientas iptables en ese host.
Task 5: Volcar el ruleset en vivo y buscar tus huellas
cr0x@server:~$ nft list ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iifname "lo" accept
tcp dport 22 accept
counter drop
}
}
table ip nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
}
chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
}
}
Significado: Tu inet filter existe, la política de input es drop y SSH está permitido. La tabla NAT también existe.
Decisión: Si tus reglas están ausentes, no estás cargando el archivo correcto o te están sobrescribiendo. Si están presentes pero el tráfico aún fluye incorrectamente, pasa a la validación de hook/ruta y al trazado.
Task 6: Identificar todas las cadenas base y prioridades en cada hook
cr0x@server:~$ nft -a list chains
table inet filter {
chain input { type filter hook input priority 0; policy drop; }
chain forward { type filter hook forward priority 0; policy drop; }
}
table inet docker {
chain input { type filter hook input priority -10; policy accept; }
}
Significado: Hay dos cadenas base en hook input. La cadena de Docker se ejecuta antes (prioridad -10) y acepta por defecto. Tu política drop se ejecuta después y puede que nunca vea paquetes.
Decisión: O cambia prioridades, elimina la cadena base en competencia, o intégrate haciendo que Docker salte a tu cadena (o viceversa). Dejar dos cadenas base con políticas en conflicto es pedir sorpresas.
Task 7: Confirmar si firewalld está gestionando nftables
cr0x@server:~$ systemctl is-enabled firewalld
enabled
Significado: firewalld probablemente afirmará su propio estado en el arranque y en recargas, potencialmente sobrescribiendo partes del ruleset.
Decisión: Elige: gestionado por firewalld o por nftables. Si firewalld permanece, deja de editar nft directamente y usa las primitivas de firewalld. Si nftables permanece, desactiva firewalld.
Task 8: Comprobar si Docker está programando reglas iptables
cr0x@server:~$ grep -nE 'iptables|ip6tables' /etc/docker/daemon.json
12: "iptables": true,
13: "ip6tables": true,
Significado: Docker programará reglas (a través de la interfaz iptables), lo que aterriza en nft si iptables-nft está seleccionado.
Decisión: Si necesitas control estricto, considera poner "iptables": false solo si estás preparado para gestionar la conectividad de contenedores tú mismo. La mayoría de equipos no lo está, y lo descubren a las 2 a.m.
Task 9: Confirmar sysctls de forwarding y bridge netfilter
cr0x@server:~$ sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding net.bridge.bridge-nf-call-iptables
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
net.bridge.bridge-nf-call-iptables = 0
Significado: El forwarding está apagado; el tráfico en bridge puede evitar los hooks estilo iptables. Si esperabas que el host enrute o filtre tráfico bridge de contenedores, tu expectativa es incorrecta.
Decisión: Si este host es un router o realiza forwarding de contenedores, habilita los sysctls apropiados y codifícalos de forma persistente. Si no debe reenviar, déjalos apagados y ajusta tu diseño.
Task 10: Confirmar que estás filtrando el namespace correcto
cr0x@server:~$ ip netns list
cni-3f2a9c7b-1
Significado: Existe al menos un netns no raíz. El tráfico puede manejarse allí, con sus propias reglas.
Decisión: Si el tráfico problemático está dentro de ese namespace, inspecciónalo con ip netns exec y aplica reglas allí (o ajusta la ruta de política del host).
Task 11: Trazar la evaluación de paquetes para ver qué regla coincide
cr0x@server:~$ nft monitor trace
trace id 9b7e7f05 inet filter input packet: iif "ens192" ip saddr 203.0.113.50 ip daddr 192.0.2.10 tcp sport 51544 tcp dport 22
trace id 9b7e7f05 rule inet filter input handle 7: tcp dport 22 accept
Significado: El paquete llegó a tu cadena input y coincidió con la regla de aceptación de SSH (handle 7). El trazado elimina las suposiciones.
Decisión: Si el paquete coincide con una regla accept/drop inesperada, arregla el orden y los predicados. Si nunca aparece, estás trazando el hook/ruta equivocados o el tráfico está evitando el host.
Task 12: Buscar sobrescrituras silenciosas por trabajos periódicos o gestión de configuración
cr0x@server:~$ systemctl list-timers --all --no-pager | grep -E 'nft|iptables|firewall'
Sun 2025-12-28 10:00:00 UTC 1h ago Sun 2025-12-28 11:00:00 UTC 1min ago firewall-sync.timer firewall-sync.service
Significado: Existe un timer que probablemente vuelve a aplicar el estado del firewall. Tu cambio manual será revertido según el calendario.
Decisión: O integras tu cambio en esa canalización gestionada o deshabilitas el trabajo en competencia. “Solo lo edito en vivo” no es una estrategia, es una broma a tu Yo futuro.
Task 13: Confirmar que tus archivos include existen y el orden de carga en la config
cr0x@server:~$ sed -n '1,120p' /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
include "/etc/nftables.d/base.nft"
include "/etc/nftables.d/nat.nft"
include "/etc/nftables.d/docker-overrides.nft"
Significado: Tienes un ruleset modular con un flush duro al principio. Eso es sensato siempre que nada más espere conservar sus propias reglas.
Decisión: Si Docker/firewalld deben coexistir, no hagas flush ruleset incondicionalmente. En su lugar, vacía solo tus tablas, o define un contrato de integración claro.
Task 14: Verificar que una recarga no corta sesiones SSH existentes
cr0x@server:~$ sudo nft -f /etc/nftables.conf && echo "reload ok"
reload ok
Significado: El ruleset se cargó correctamente. Si tu SSH permaneció, tu regla established/related está haciendo su trabajo.
Decisión: Si la recarga mata sesiones, falta tu regla stateful accept o borraste expectativas de conntrack. Arregla antes de repetir esto de forma remota.
Tres mini-historias corporativas (y qué tomar de ellas)
Mini-historia #1: El incidente causado por una suposición equivocada
Un equipo heredó un host Debian que actuaba como jump box y un router ligero para un segmento de laboratorio. Quisieron “endurecer el ingreso” y escribieron una cadena input con default drop y una lista de permitidos. Parecía perfecto en la revisión. Las pruebas desde su estación funcionaron.
Dos horas después, otro grupo informó que el segmento de laboratorio no podía alcanzar la caché de artefactos. La caché vivía al otro lado del jump box. Nadie relacionó eso con el cambio del firewall porque “solo tocamos input”.
La realidad: casi todo el tráfico relevante se reenviaba a través de la caja, nunca golpeando input. La cadena forward por defecto seguía siendo lo que Docker y el bagaje histórico de iptables dejaron atrás. En otras palabras, aseguraron la puerta equivocada.
La corrección no fue heroica. Añadieron una cadena base deliberada forward en table inet filter con permisos explícitos entre segmentos y un default drop, luego validaron con nft monitor trace desde ambos lados. También documentaron las suposiciones de la ruta del paquete en la solicitud de cambio, lo cual suena aburrido hasta que evita que la siguiente persona repita el mismo error.
Mini-historia #2: La optimización que salió mal
Un equipo de plataforma decidió “estandarizar y acelerar el arranque” cargando nftables muy temprano, antes de la inicialización de red, en toda la flota. Su razonamiento tenía sentido: firewall temprano, ventana de riesgo menor. Así que fijaron un ordering agresivo y eliminaron dependencias de red de la unidad.
Funcionó en staging, mayormente. En producción, un subconjunto de máquinas tenía renombres de interfaz previsibles que se completaban más tarde en el arranque. Las reglas referenciaban coincidencias iifname para una interfaz de gestión y una de almacenamiento. La carga temprana hizo que esos nombres no fueran estables todavía. Las reglas se compilaron, pero no coincidían con nada. Los hosts arrancaron en una realidad permisiva que no pretendían.
Lo detectaron solo porque un ingeniero notó que nft list ruleset mostraba las reglas esperadas, sin embargo el tráfico claramente no se filtraba. El trazado reveló paquetes golpeando el accept por defecto en una cadena distinta que debía ser inalcanzable.
La lección del retroceso: “cargar temprano” no es automáticamente “seguro”. Es seguro solo si lo que coincides es estable desde temprano. Acabaron moviendo reglas dependientes de interfaz a sets indexados por subredes y usando tablas inet, y ordenaron nftables después de udev settle en esa clase de hardware. Un poco más tarde, de hecho correcto.
Mini-historia #3: La práctica aburrida pero correcta que salvó el día
En otra compañía, un equipo SRE tenía un hábito soso: cada cambio de firewall incluía una instantánea antes/después de nft list ruleset, más un sencillo script de prueba de conectividad ejecutado desde dos puntos de observación. Guardaban estos artefactos con el registro de despliegue. Nadie lo presumía en las fiestas.
Durante una actualización rutinaria del SO en hosts Debian, un paquete nuevo trajo firewalld como dependencia para una herramienta “de conveniencia”. No fue malicioso; fue por defecto. firewalld se habilitó en el arranque para una pequeña fracción de la flota debido a un comportamiento post-install y una rareza de automatización.
En una hora, alguien notó que el ruleset en vivo no coincidía con la instantánea esperada después del reinicio. No porque el tráfico ya se hubiera roto—porque tenían el hábito de comparar el estado tras mantenimiento. Deshabilitaron firewalld en esos nodos, reafirmaron nftables y añadieron un pin de paquete explícito más una comprobación CI que falla si firewalld está habilitado en ese rol.
La lección que salvó el día: las operaciones fiables son mayoritariamente hábitos y guardarraíles. La depuración sofisticada es para cuando fallan los guardarraíles.
Errores comunes: síntoma → causa raíz → arreglo
1) “Las reglas están presentes, pero los paquetes las ignoran”
Síntoma: nft list ruleset muestra tus reglas de drop, pero las conexiones aún funcionan.
Causa raíz: Tu regla está en el hook equivocado (input vs forward) o en la familia equivocada (ip vs inet), o una cadena base diferente se ejecuta antes y acepta.
Arreglo: Usa nft -a list chains para enumerar cadenas base y prioridades; usa nft monitor trace para verificar la evaluación; ajusta hook/prioridad y elimina cadenas base en competencia.
2) “Funciona hasta el reinicio, luego desaparece”
Síntoma: Tras el reinicio, el ruleset está vacío o por defecto.
Causa raíz: nftables deshabilitado, archivo de configuración no referenciado, error de parseo en el arranque, o otro gestor sobrescribió el estado tras cargar nftables.
Arreglo: Asegura systemctl enable nftables; valida con nft -c -f; revisa el journal por fallos; audita firewalld/Docker/unidades iptables-restore y timers.
3) “Mi archivo include carga manualmente pero falla en el servicio”
Síntoma: Manual nft -f funciona; la carga en arranque falla.
Causa raíz: Rutas include relativas, archivo faltante en el arranque, directorio de trabajo diferente, o problema de orden donde el archivo se genera más tarde.
Arreglo: Usa rutas absolutas en include; asegura que la unidad que genera el archivo se ejecute antes de nftables; añade Requires= y After= si es necesario.
4) “La red de Docker se rompe al activar default drop”
Síntoma: Los contenedores pierden acceso saliente o fallan los puertos publicados.
Causa raíz: Docker espera cierto comportamiento de forward/NAT; tus reglas caen el tráfico reenviado o bloquean flujos inter-bridge; las cadenas de Docker pueden ejecutarse antes que las tuyas.
Arreglo: Permite explícitamente established/related en forward; permite las interfaces bridge necesarias y las cadenas de Docker; evita flush completo del ruleset si Docker debe gestionar sus piezas.
5) “iptables muestra una cosa, nft muestra otra”
Síntoma: iptables -S no coincide con nft list ruleset.
Causa raíz: El backend iptables-legacy está activo, o estás mezclando herramientas entre namespaces.
Arreglo: Alinea alternatives a iptables-nft si quieres estado unificado; deja de usar herramientas legacy; verifica el contexto de namespace.
6) “Un agente proveedor mantiene revirtiendo mis reglas”
Síntoma: Tus reglas se aplican, luego se revierten en intervalos extraños.
Causa raíz: Reaplicación periódica (timer systemd, gestión de configuración, agente de seguridad cloud).
Arreglo: Localiza el timer/servicio; modifica la configuración fuente; añade monitorización de deriva de reglas (hashea la salida de nft list ruleset).
Listas de verificación / plan paso a paso
Paso a paso: hacer de nftables la única fuente de la verdad (recomendado para servidores)
- Elige el gestor. Si quieres nftables simple, deshabilita firewalld y deja de usar scripts iptables como configuración canónica.
- Alinea el backend de iptables. Usa iptables-nft para que Docker y cualquier consumidor de iptables aterricen en el mundo nft, a menos que tengas una razón fuerte en contra.
- Diseña la estructura del ruleset. Prefiere
table inet filterpara input/forward/output. Mantén NAT entable ip nat(yip6 natsolo si realmente haces NAT IPv6). - Decide si puedes hacer flush con seguridad. Si nada más debe gestionar reglas,
flush rulesetes limpio. Si Docker debe gestionar, vacía solo tus tablas. - Codifica el orden. Si dependes de sysctls o archivos generados, systemd debe saberlo.
- Verifica con trace y contadores. Añade contadores a reglas clave y observa incrementos durante pruebas.
- Hazlo persistente. Habilita la unidad nftables y mantiene la config en una ruta de archivos gestionada.
- Añade detección de deriva. Alerta si el hash del ruleset en vivo cambia fuera de ventanas de cambio.
Lista de verificación: procedimiento seguro para cambios remotos
- Confirma que tienes acceso a consola o fuera de banda (BMC, consola del hipervisor).
- Asegura que
ct state established,related acceptexista antes de poner default-drop en input. - Usa
nft -c -fantes denft -f. - Aplica cambios durante una sesión con una segunda conexión como canario.
- Ejecuta inmediatamente
nft monitor tracemientras pruebas un flujo representativo. - Registra
nft list rulesetantes/después para depuración sin culpas posterior.
Lista de verificación: reglas de coexistencia (cuando no puedes evitar Docker/firewalld)
- Si firewalld está habilitado, trátalo como la fuente de verdad y configúralo directamente.
- Si Docker está habilitado y mantienes
"iptables": true, no vacíes todo el ruleset en recargas. - Inspecciona explícitamente las prioridades de cadena para asegurar que tu política no sea evadida por cadenas base anteriores.
- Prefiere integrarte con las cadenas de Docker mediante jumps en lugar de reescribirlas.
Preguntas frecuentes
¿Por qué mis reglas funcionan cuando ejecuté nft -f manualmente pero no después del reinicio?
Porque el arranque es una carrera. Tu servicio puede fallar por una ruta include, cargarse antes de que existan archivos, o ser sobrescrito después de la carga por Docker/firewalld/timers. Revisa systemctl status nftables y el journal primero.
¿Debo usar table inet o tablas separadas table ip/table ip6?
Usa inet para reglas de filtrado a menos que tengas una razón concreta para no hacerlo. Reduce la deriva entre v4/v6 y evita incidentes de “IPv6 accidentalmente abierto”.
¿Es seguro poner flush ruleset al principio de /etc/nftables.conf?
Solo es seguro si nftables es el gestor único. Si Docker o firewalld programan reglas, vaciarlas todas eliminará sus cadenas y pasarás la tarde explicando por qué los contenedores no llegan al DNS.
¿Cómo sé si Docker es lo que cambia mi firewall?
Busca tablas/cadenas creadas por Docker en nft list ruleset y correlaciónalo con journalctl -u docker. También revisa /etc/docker/daemon.json por "iptables": true.
¿Puedo ejecutar firewalld y un ruleset nft escrito a mano juntos?
Puedes, pero no deberías. firewalld afirmará el estado; tus cambios escritos a mano se vuelven no deterministas. Elige un gestor por rol de host.
¿Por qué iptables -S no coincide con nft list ruleset?
Puedes estar usando iptables-legacy. Comprueba update-alternatives --display iptables. También verifica namespaces de red—iptables en otro namespace mostrará estado distinto.
Mi SSH se cortó durante una recarga. ¿Qué hice mal?
Probablemente pusiste default-drop en input sin permitir ct state established,related lo suficientemente temprano, o reemplazaste el ruleset sin preservar la aceptación stateful. Arregla el orden: established/related primero, luego loopback, luego servicios permitidos, luego drop.
¿Cómo demuestro qué regla droppea un paquete?
Usa nft monitor trace durante un flujo de prueba. Muestra la cadena y el handle de la regla que coincidió. Los contadores ayudan, pero trace es lo más claro.
¿Cuál es la forma más limpia de prevenir la deriva de reglas?
Haz que un sistema sea la fuente de verdad (archivo nftables, firewalld o gestión de configuración), luego añade monitorización que hashee nft list ruleset y alerte sobre cambios inesperados.
Conclusión: próximos pasos para prevenir recurrencias
Si nftables “no funciona” en Debian 13, casi nunca es un misterio del kernel. Es ambigüedad en el plano de control: orden de carga, gestores múltiples o cadenas que se ejecutan en un orden que no pretendías.
Haz lo siguiente:
- Ejecuta la guía rápida de diagnóstico y captura el ruleset en vivo, prioridades de cadena y la línea temporal del journal.
- Elige un único gestor de firewall por rol de host. Deshabilita los demás o intégralos explícitamente.
- Elimina carreras accidentales: valida la config, usa includes absolutos y codifica el orden de systemd cuando existan dependencias.
- Usa trace una vez por incidente. Es la forma más rápida de dejar de debatir teorías.
- Añade detección de deriva para que detectes sobrescrituras antes que los usuarios.
El estado final que quieres es aburrido: comportamiento determinista en el arranque, una única fuente de verdad y cambios en el firewall que se comporten igual el martes que en tus pruebas del viernes.