SSD Nodes Learn 🎉 VPS desde $5.50/mes
Guías Matt ConnorPor Matt Connor

iptables frente a nftables en Ubuntu: qué se ejecuta

En Ubuntu, iptables puede usar el backend nftables. Compruébelo con iptables -V, lea el ruleset nativo y evite conflictos entre ufw y Docker.

iptables frente a nftables en Ubuntu: ¿cuál está ejecutando su servidor?

En Ubuntu 20.04 y posteriores, el comando iptables es una interfaz que escribe reglas de nftables. En el kernel se ejecuta un solo filtro de paquetes, nftables, y dos comandos de espacio de usuario lo programan. Una línea iptables -A INPUT sigue funcionando exactamente igual, y la regla que crea es una regla de nftables que nft puede mostrar.

Confírmelo en su propio servidor antes de darlo por cierto.

iptables -V
sudo update-alternatives --display iptables
sudo nft list ruleset

En Ubuntu 24.04 (iptables 1.8.10, en agosto de 2026), iptables -V muestra iptables v1.8.10 (nf_tables). El nombre entre corchetes es el backend. (nf_tables) significa que el comando se comunica con nftables. (legacy) significa el antiguo backend x_tables, que Ubuntu todavía distribuye como iptables-legacy y que el kernel mantiene como un conjunto de reglas completamente independiente. update-alternatives muestra el enlace simbólico que determina esa elección: link currently points to /usr/sbin/iptables-nft.

En un VPS nuevo sin ningún firewall configurado, sudo nft list ruleset no muestra nada. Esa salida vacía es la línea base. Añada una regla de la forma antigua y vuelva a comprobarlo.

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
  chain INPUT {
    type filter hook input priority filter; policy accept;
    tcp dport 22 counter packets 0 bytes 0 accept
  }
  chain FORWARD {
    type filter hook forward priority filter; policy accept;
  }
  chain OUTPUT {
    type filter hook output priority filter; policy accept;
  }
}

Su regla de iptables es una regla de nftables. iptables-nft identifica las tablas que crea y nft muestra ese aviso cuando detecta la marca, porque editar una tabla de ese tipo con nft hace que dos herramientas administren las mismas reglas. Compruebe qué produjo un solo comando: una tabla que no nombró y cadenas que no solicitó. Ese es el modelo antiguo, y es lo primero que cambia cuando escribe las reglas directamente con nftables.

Qué te oculta iptables -L

iptables -L muestra sólo la tabla de filter. Las reglas de NAT (traducción de direcciones de red) necesitan iptables -t nat -L, y las reglas de mangle necesitan -t mangle. IPv6 se gestiona con un comando independiente, ip6tables, que tiene su propia copia de cada regla. Por tanto, un sistema puede parecer limpio en una lista mientras otra tabla que no has comprobado descarta o reescribe tus paquetes.

sudo nft list ruleset muestra todas las familias, todas las tablas, todas las cadenas y todas las reglas en una sola salida. En un servidor que no has configurado tú mismo, ese comando es la forma más rápida de ver lo que está cargado realmente. Añade -a para mostrar los identificadores de las reglas. Los necesitas para eliminar una regla sin borrar toda la cadena.

Conviene corregir dos hábitos mientras estás aquí. iptables -L resuelve las direcciones y los puertos a nombres, por lo que en un sistema con el resolvedor averiado parece que se ha bloqueado: usa iptables -nvL. Confirma también que el backend heredado esté vacío con sudo iptables-legacy -nvL, porque si hay reglas en ambos backends el kernel evalúa los dos y ninguna de las listas muestra toda la configuración.

Tablas y cadenas que se crean, no que se heredan

nftables empieza sin nada. No existe ninguna tabla filter hasta que se crea, y filter es sólo un nombre elegido por usted. Una cadena sólo recibe paquetes cuando se le asignan un tipo, un hook y una prioridad, lo que la convierte en una cadena base. Una cadena sin esos elementos sólo se alcanza mediante un jump o un goto explícito, por lo que no consume recursos hasta que algo salta a ella.

El otro cambio importante es la familia inet. Una tabla inet gestiona IPv4 e IPv6 con las mismas reglas. Esto elimina toda una clase de errores en los que un puerto está cerrado en iptables y completamente abierto en ip6tables. Esta discrepancia es lo bastante común como para tener su propio modo de fallo en servidores con ufw.

Este es un conjunto de reglas completo para un servidor. Se guarda en /etc/nftables.conf.

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.5, 198.51.100.0/24 }
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept
    tcp dport 22 ip saddr @admin_ips counter accept
    tcp dport { 80, 443 } counter accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}

Lea dos veces la línea 2. flush ruleset elimina todas las tablas del servidor, incluidas las tablas que ufw y Docker hayan creado para su propio uso. Siga leyendo antes de ejecutarlo en un servidor en producción.

La primera regla de la cadena input hace la mayor parte del trabajo. ct state established,related accept permite que vuelvan las respuestas a las conexiones que inició, de modo que el resto de la cadena sólo tiene que decidir sobre las conexiones nuevas. ct state invalid drop descarta los paquetes que no coinciden con ninguna conexión conocida ni con un inicio válido. Todo lo que queda después es una excepción explícita, y policy drop gestiona el resto.

Compruebe el archivo antes de cargarlo y mantenga abierta una segunda sesión SSH mientras lo hace. policy drop, junto con un solo error tipográfico en la regla de SSH, puede bloquearle el acceso a su propio servidor.

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

nft -c -f analiza el archivo e informa de los errores sin cargar nada. Un análisis correcto no muestra ninguna salida.

Los conjuntos sustituyen listas largas de reglas

tcp dport { 80, 443 } es un conjunto anónimo: una regla y una consulta, en lugar de una regla por puerto. Un conjunto con nombre, como admin_ips, permite ir más allá porque puede cambiarlo mientras el firewall está en ejecución.

sudo nft add element inet filter admin_ips { 203.0.113.9 }
sudo nft list set inet filter admin_ips
sudo nft delete element inet filter admin_ips { 203.0.113.9 }

No es necesario recargar ni renumerar reglas, y la coincidencia sigue siendo una sola consulta tanto si el conjunto contiene cinco direcciones como si contiene cincuenta mil. flags interval permite que un conjunto contenga rangos y prefijos CIDR (enrutamiento entre dominios sin clase), como 198.51.100.0/24. Sin ese indicador, el conjunto sólo acepta direcciones individuales y la carga del prefijo falla.

Los conjuntos también pueden hacer que sus propios elementos caduquen.

set banned {
  type ipv4_addr
  flags timeout
  timeout 1h
}

Con una regla ip saddr @banned drop, cada elemento se elimina una hora después de añadirse. Así es como la acción de nftables en fail2ban en Ubuntu 24.04 bloquea una dirección: añade un elemento a un conjunto, no añade una regla. Si los puertos todavía son un concepto nuevo, empiece por qué es realmente un puerto en Linux.

Hay una diferencia que suele causar problemas durante una migración. nftables no cuenta los paquetes a menos que se lo solicite. iptables -nvL muestra siempre los contadores de todas las reglas. En nftables, sólo las reglas que contienen la palabra clave counter tienen números, así que añada counter a cualquier regla que prevea depurar más adelante.

Cómo determinan los hooks y las prioridades el orden

Una cadena base nombra un hook, que es el punto de la ruta del paquete en el que se ejecuta. prerouting se ejecuta antes de la decisión de enrutamiento. input se ejecuta para los paquetes dirigidos a esta máquina. forward se ejecuta para los paquetes enrutados a través de ella. output se ejecuta para los paquetes generados por procesos locales. postrouting se ejecuta al final, justo antes de que salga el paquete.

La prioridad ordena las cadenas dentro de un mismo hook, empezando por el número más bajo. nftables asigna nombres a los valores clásicos: raw es -300, mangle es -150, dstnat es -100, filter es 0 y srcnat es 100. Escribir priority filter; equivale a escribir priority 0;.

Ahora viene la parte que determina si se pueden mezclar herramientas. Todas las cadenas base registradas en un hook se ejecutan en orden de prioridad. Un paquete aceptado en su cadena no termina ahí: accept sólo termina esa cadena y el paquete continúa hasta la siguiente cadena base del mismo hook. drop es definitivo en cualquier contexto y detiene el paquete de inmediato. Por tanto, una regla permisiva en su tabla no puede deshacer un drop en la tabla de ufw, independientemente de cuál se ejecute primero, y su accept no le protege de una cadena que se ejecute después.

Dos cadenas base del mismo hook con la misma prioridad se ejecutan en el orden de registro, que depende de qué servicio se inició primero. Ese orden puede cambiar después de un reinicio. Si debe ejecutar su propia tabla junto a ufw, asígnele una prioridad distinta para que el orden quede definido en lugar de depender de una condición de carrera.

¿Por qué no hay ninguna regla de NAT inverso que escribir?

Esta es la pregunta que más se responde mal, así que aquí está la respuesta directa. El seguimiento de conexiones escribe la traducción inversa automáticamente. No hay que añadir una segunda regla.

Una tabla nat que realiza las dos partes habituales del trabajo de un VPS tiene este aspecto.

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "enp1s0" tcp dport 8080 dnat ip to 10.0.0.5:80
  }

  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.0.0.0/24 oifname "enp1s0" masquerade
  }
}

Sólo se evalúa el primer paquete de una conexión contra una cadena nat. Cuando una regla coincide, el kernel almacena esa traducción en la tabla de seguimiento de conexiones junto con la entrada de la conexión. Todos los paquetes posteriores, en ambas direcciones, se reescriben a partir de la entrada almacenada y no se vuelve a leer ninguna regla. Instale la herramienta conntrack y consulte una entrada activa.

sudo apt install -y conntrack
sudo conntrack -L -p tcp
tcp 6 431999 ESTABLISHED src=198.51.100.20 dst=203.0.113.10 sport=54321 dport=8080 src=10.0.0.5 dst=198.51.100.20 sport=80 dport=54321 [ASSURED] mark=0 use=1

Léala como dos tuplas. Los cuatro primeros campos son la conexión tal como la envió el cliente, dirigida a 203.0.113.10:8080, su dirección pública. Los cuatro siguientes son la respuesta que espera el kernel, ya invertida y ya traducida, procedente de 10.0.0.5:80, el backend real. Esa segunda tupla es la regla inversa. El kernel la escribió cuando coincidió el primer paquete.

Por tanto, no escriba una regla para la dirección de retorno. No puede coincidir, porque los paquetes de retorno pertenecen a una conexión establecida y nunca llegan a una cadena nat; y, si de algún modo lo hicieran, traduciría un paquete que el kernel ya ha corregido.

La ubicación de una reescritura se deduce del mismo mecanismo. La traducción de destino debe ejecutarse en prerouting, antes de la decisión de enrutamiento, porque el enrutamiento debe ver el nuevo destino o el paquete irá al lugar equivocado. El tráfico que genera el propio equipo se gestiona en el hook output por la misma razón. La traducción de origen, incluida la reescritura del puerto de origen, debe ejecutarse en postrouting, después de que el enrutamiento haya elegido la interfaz de salida. masquerade obtiene su dirección de esa interfaz, y la interfaz no se conoce hasta que se ejecuta el enrutamiento.

Por eso una regla como esta pertenece al final de la ruta y a ningún otro sitio.

ip saddr 10.0.0.0/24 oifname "enp1s0" snat ip to 203.0.113.10:20000-30000

El rango de puertos reescribe el puerto de origen junto con la dirección de origen. Esto es lo que se necesita cuando muchos clientes internos comparten una dirección pública y sus puertos de origen entran en conflicto. La respuesta llega dirigida a un puerto de ese rango, conntrack la asocia con la entrada, y el puerto de origen original se restaura antes de entregar el paquete. De nuevo, no hace falta una segunda regla.

Hay una consecuencia práctica: cambiar una regla de NAT no mueve las conexiones que ya existen, porque su traducción ya está almacenada. Mantienen el comportamiento anterior hasta que sus entradas caducan. sudo conntrack -D -p tcp --dport 8080 elimina las entradas coincidentes y sudo conntrack -F las elimina todas. Use la segunda opción con cuidado en un equipo que haga NAT, porque esas traducciones almacenadas son las que mantienen activas las conexiones actuales; vaciarlas interrumpe de una vez todas las conexiones que atraviesan el equipo.

ufw y Docker escriben sus propias reglas

ufw es una interfaz para iptables, que en Ubuntu es una interfaz para nftables. Por tanto, un sistema con ufw tiene una tabla ip filter con cadenas llamadas ufw-before-input, ufw-user-input y sucesivas, además de una copia ip6 filter de la misma estructura. Compruébelo con sudo nft list ruleset | grep ufw. Esas cadenas se generan a partir de los archivos de /etc/ufw, y ufw reload las vuelve a escribir desde cero. Por eso, una regla iptables escrita manualmente y añadida al final desaparece en la siguiente recarga. Los conceptos básicos de ufw para un VPS explican esa estructura de archivos.

Docker configura el firewall directamente y no consulta ufw. Publicar un puerto con -p 80:80 escribe una regla DNAT en la tabla nat y una regla de aceptación en la ruta de reenvío. Ambas se ejecutan antes que las cadenas de usuario de ufw. El resultado sorprende la primera vez: se carga ufw deny 80 y el contenedor sigue siendo accesible desde Internet. La solución está en la cadena DOCKER-USER, que Docker deja disponible para sus reglas, y por qué los contenedores de Docker ignoran ufw lo explica paso a paso. Consulte las reglas del sistema con sudo nft list ruleset | grep -i docker.

Vuelva a leer ahora la línea flush ruleset de la configuración anterior. Elimina todas las tablas, incluidas las tablas que administran esas dos herramientas. En un host de Docker, los puertos publicados dejan de funcionar hasta que sudo systemctl restart docker reconstruye las cadenas. Esa línea es la forma más habitual de dejar fuera de servicio los propios servicios al limpiar un firewall.

Reglas que sobreviven a un reinicio

Ninguno de los dos conjuntos de reglas es persistente por sí solo. El kernel lo olvida todo al apagar el sistema, y cada opción lo resuelve con un paquete independiente.

En nftables, /etc/nftables.conf es leído por nftables.service. Ubuntu instala ese servicio deshabilitado, así que compruébelo antes de confiar en él.

systemctl is-enabled nftables
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables

Para iptables, el paquete es iptables-persistent, que instala netfilter-persistent y guarda las reglas en /etc/iptables/rules.v4 y /etc/iptables/rules.v6.

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

No ejecute ambos. Dos archivos que afirman contener el firewall terminarán desincronizados, y el que se cargue en último lugar prevalecerá de una forma que nadie puede predecir leyendo cualquiera de los dos archivos.

Hay una trampa relacionada al volcar un conjunto de reglas activo. sudo nft -s list ruleset > /etc/nftables.conf captura todo lo que está cargado en ese momento, incluidas las tablas de ufw y Docker. Si restaura ese volcado durante el arranque, obtiene una copia congelada de las reglas que esas herramientas esperan crear por sí mismas y, después, una segunda copia cuando se inician. Vuelque únicamente su propia tabla con sudo nft -s list table inet filter. La opción -s excluye los contadores, que no deben incluirse en un archivo de configuración.

¿Debería cambiar a la configuración nativa en su VPS?

Deje ufw como está, salvo que necesite algo que no pueda expresar. ufw cubre la función habitual de un VPS: denegar todo de forma predeterminada y abrir unos pocos puertos. Sustituirlo por un conjunto de reglas escrito manualmente no aporta nada y añade otro elemento que mantener.

Use la configuración nativa cuando necesite algo que esté fuera del modelo de ufw: NAT y reenvío de puertos, conjuntos que se actualicen durante la ejecución, una regla que cubra ambas familias de direcciones o prioridades de cadenas que pueda elegir directamente. Son motivos válidos, y ufw no puede expresar ninguno de ellos.

Si usa la configuración nativa, hágalo por completo. Ejecute sudo ufw disable y sudo systemctl disable --now ufw, confirme con sudo nft list ruleset que sus tablas hayan desaparecido y, después, cargue su propio archivo. Un sistema que ejecuta ufw y una tabla escrita manualmente al mismo tiempo sigue permitiendo tráfico, pero la política activa es ahora la unión de dos conjuntos de reglas que se evalúan en un orden determinado por el arranque de los servicios. Nadie que lea uno de los dos archivos puede saber qué hace realmente el sistema.

Migración de un conjunto de reglas de iptables

iptables-translate convierte una regla y muestra su formato de nftables. No modifica nada en el sistema.

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 counter accept

iptables-restore-translate -f /etc/iptables/rules.v4 hace lo mismo con un conjunto de reglas guardado completo. Considere su salida como un primer borrador. La conversión es mecánica y se realiza regla por regla. Por eso conserva los nombres antiguos de tablas y cadenas, devuelve conjuntos de reglas separados para IPv4 e IPv6 y no incluye ninguno de los conjuntos que justificaban la migración. Reescríbalo manualmente como una sola tabla de inet y compruébelo con nft -c -f antes de usarlo en un servidor en producción.

Las direcciones de estos ejemplos pertenecen a los rangos reservados para documentación 203.0.113.0/24 y 198.51.100.0/24, y enp1s0 es un nombre de interfaz. Obtenga los valores propios de ip route show default y ip -br addr en lugar de copiar los míos, porque las imágenes actuales de Ubuntu rara vez llaman eth0 a una interfaz.

FAQ

¿iptables está obsoleto en Ubuntu?

El comando no va a desaparecer y sigue funcionando en Ubuntu 24.04. Lo que ha cambiado es lo que ocurre internamente: iptables es un front end que escribe reglas de nftables mediante el back end iptables-nft. Compruébelo con iptables -V, que muestra iptables v1.8.10 (nf_tables) en 24.04. El antiguo back end x_tables sigue disponible como iptables-legacy y mantiene un conjunto de reglas completamente independiente. Por tanto, coloque las reglas en un solo back end, no en ambos.

¿Necesito una segunda regla para deshacer NAT en el trayecto de vuelta?

No. El seguimiento de conexiones guarda la traducción cuando el primer paquete de una conexión coincide con una regla de nat. Después, todos los paquetes posteriores en ambas direcciones se reescriben a partir de esa entrada almacenada. sudo conntrack -L lo muestra como dos tuplas por conexión: primero la dirección original y después la respuesta ya revertida. Una regla escrita para la dirección de retorno no sirve, porque los paquetes de retorno nunca llegan a una cadena nat.

¿Puedo ejecutar ufw y mis propias reglas de nftables al mismo tiempo?

Funciona, pero crea un problema. Se ejecutan todas las cadenas base asociadas a un hook. Por tanto, la política activa es la combinación de ambos conjuntos de reglas, ordenada por prioridad y, con la misma prioridad, según el servicio que se haya iniciado primero. Un drop en cualquiera de los dos es definitivo, y un accept en sus reglas no impide que el otro conjunto descarte el mismo paquete. Elija una sola herramienta. Si es nftables, desactive ufw primero y confirme con sudo nft list ruleset que sus tablas hayan desaparecido.

¿Cómo hago que las reglas de nftables sobrevivan a un reinicio en Ubuntu?

Coloque el conjunto de reglas en /etc/nftables.conf, compruébelo con sudo nft -c -f /etc/nftables.conf y después ejecute sudo systemctl enable --now nftables. El servicio no está habilitado de forma predeterminada, por lo que conviene ejecutar systemctl is-enabled nftables una vez. Cuando genere ese archivo, vuelque sólo su propia tabla con sudo nft -s list table inet filter, porque un volcado completo de list ruleset también captura las tablas que ufw y Docker administran por separado.