nftables es el sustituto de iptables en el kernel Linux: una sola herramienta (nft) y una sola sintaxis para IPv4, IPv6, ARP y bridges, con conjuntos y mapas nativos que evitan repetir reglas. En este tutorial escribirás un firewall de entrada completo en /etc/nftables.conf para un servidor con SSH y web en Ubuntu 24.04, lo aplicarás con un mecanismo de reversión automática, lo gestionarás en caliente y verás cómo traducir reglas antiguas de iptables.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. En Debian 12 los pasos son idénticos; en Rocky Linux 9 el servicio y el archivo son los mismos, pero el firewall por defecto es firewalld y debes desactivarlo antes.
  • Un usuario no root con privilegios sudo.
  • Acceso a la consola del servidor (VNC o consola web del proveedor) por si pierdes la conexión SSH.
  • Conocer los puertos que debe ofrecer el servidor. En el ejemplo: SSH (22), HTTP (80) y HTTPS (443).

Paso 1: Instalar nftables y desactivar UFW

Instala el paquete, que incluye la herramienta nft, el archivo /etc/nftables.conf y el servicio que lo carga en el arranque:

sudo apt update
sudo apt install nftables

Comprueba la versión:

sudo nft --version
nftables v1.0.9 (Old Doc Yellowstone)

Si UFW está activo, desactívalo:

sudo ufw disable

Revisa qué reglas hay cargadas ahora mismo. En un sistema limpio la salida estará vacía:

sudo nft list ruleset

Paso 2: Entender la estructura de nftables

nftables no trae tablas ni cadenas predefinidas como iptables: las creas tú. La jerarquía es:

  • Tabla: contenedor de cadenas, asociado a una familia. La familia inet cubre IPv4 e IPv6 a la vez, y es la que usarás.
  • Cadena base: una cadena enganchada a un punto del kernel (hook), con prioridad y política. input filtra el tráfico que va al servidor, forward el que lo atraviesa y output el que sale de él.
  • Regla: condiciones y una acción (accept, drop, reject, jump...).
  • Conjuntos (set) y mapas: listas de direcciones, puertos o pares con las que una sola regla sustituye a decenas.

Comparación rápida con iptables:

iptablesnftables
iptables + ip6tables por separadoUna tabla inet para ambos
Tablas y cadenas fijasTablas y cadenas definidas por ti
Una regla por IP o puertoConjuntos: tcp dport { 80, 443 }
-m conntrack --ctstate ESTABLISHEDct state established
iptables-save / iptables-restorenft list ruleset / nft -f archivo

Paso 3: Escribir el ruleset

Toda la configuración vivirá en un archivo que se carga de forma atómica: o se aplica entero o no se aplica nada. Haz una copia del archivo de ejemplo y ábrelo:

sudo cp /etc/nftables.conf /etc/nftables.conf.orig
sudo nano /etc/nftables.conf

Sustituye su contenido por el siguiente:

#!/usr/sbin/nft -f

flush ruleset

define SSH_PORT = 22
define WEB_PORTS = { 80, 443 }

table inet filter {
    # IP con acceso completo (oficina, VPN, monitorización)
    set trusted4 {
        type ipv4_addr
        flags interval
        elements = { 198.51.100.20 }
    }

    # IP bloqueadas manualmente
    set blocklist4 {
        type ipv4_addr
        flags interval
    }

    # Contadores dinámicos para limitar conexiones nuevas a SSH por IP
    set ssh_meter4 {
        type ipv4_addr
        flags dynamic
        timeout 1m
    }

    set ssh_meter6 {
        type ipv6_addr
        flags dynamic
        timeout 1m
    }

    chain input {
        type filter hook input priority filter; policy drop;

        iif "lo" accept
        ct state established,related accept
        ct state invalid drop

        ip saddr @blocklist4 drop
        ip saddr @trusted4 accept

        # ICMPv6 es necesario para que IPv6 funcione
        meta l4proto ipv6-icmp accept
        icmp type echo-request limit rate 5/second accept

        tcp dport $SSH_PORT ct state new add @ssh_meter4 { ip saddr limit rate over 10/minute } drop
        tcp dport $SSH_PORT ct state new add @ssh_meter6 { ip6 saddr limit rate over 10/minute } drop
        tcp dport $SSH_PORT accept

        tcp dport $WEB_PORTS accept

        limit rate 5/minute log prefix "nft-drop: " level warn
    }

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

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

Qué hace cada parte:

  • flush ruleset al principio vacía todo antes de cargar, de modo que el archivo describe el estado completo del firewall y recargarlo es idempotente.
  • define crea variables que puedes reutilizar con $NOMBRE.
  • ct state established,related accept deja pasar las respuestas a conexiones que el servidor inició. Sin esta regla nada funcionaría con la política drop.
  • trusted4 y blocklist4 son conjuntos con flags interval, que admiten tanto IP sueltas como rangos (203.0.113.0/24). Podrás cambiarlos en caliente sin tocar reglas.
  • Las reglas de SSH añaden cada IP de origen a un conjunto dinámico con un limitador propio. Si una IP abre más de 10 conexiones nuevas por minuto, las siguientes se descartan. Hay un conjunto por familia porque cada uno guarda un tipo de dirección.
  • La última regla registra, como máximo 5 veces por minuto, lo que va a descartar la política.

Si no tienes una IP de confianza fija, deja el conjunto trusted4 sin la línea elements.

Paso 4: Validar y aplicar con reversión automática

Comprueba la sintaxis sin aplicar nada con -c:

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

Si no muestra nada, el archivo es válido. Antes de cargarlo, programa una reversión: un temporizador de systemd que vaciará el ruleset (dejando el servidor abierto) dentro de 3 minutos si no lo cancelas. Así, si una regla te deja sin SSH, recuperas el acceso solo:

sudo systemd-run --on-active=3min --unit=nft-rollback /usr/sbin/nft flush ruleset
Running timer as unit: nft-rollback.timer
Will run service as unit: nft-rollback.service

Aplica el ruleset:

sudo nft -f /etc/nftables.conf

Abre una segunda sesión SSH desde otra terminal. Si puedes entrar, cancela la reversión:

sudo systemctl stop nft-rollback.timer

Si no puedes entrar, espera a que pasen los 3 minutos, vuelve a conectar y revisa el archivo.

Comprueba el ruleset cargado:

sudo nft list chain inet filter input
table inet filter {
	chain input {
		type filter hook input priority filter; policy drop;
		iif "lo" accept
		ct state established,related accept
		ct state invalid drop
		...

Paso 5: Cargar el firewall en cada arranque

El servicio nftables carga /etc/nftables.conf al iniciar el sistema, pero en Ubuntu viene desactivado. Habilítalo y arráncalo:

sudo systemctl enable --now nftables

Verifica su estado:

systemctl status nftables --no-pager
● nftables.service - nftables
     Loaded: loaded (/usr/lib/systemd/system/nftables.service; enabled; preset: enabled)
     Active: active (exited) since Thu 2026-09-24 11:02:17 UTC; 4s ago

active (exited) es el estado correcto: el servicio carga las reglas y termina. A partir de ahora, para aplicar cambios en el archivo usa sudo systemctl reload nftables (o sudo nft -f /etc/nftables.conf).

Paso 6: Gestionar el firewall en caliente

Los conjuntos permiten bloquear o permitir IP sin recargar el archivo. Bloquea una IP y un rango:

sudo nft add element inet filter blocklist4 { 192.0.2.77, 203.0.113.0/24 }

Consulta el contenido del conjunto:

sudo nft list set inet filter blocklist4
table inet filter {
	set blocklist4 {
		type ipv4_addr
		flags interval
		elements = { 192.0.2.77, 203.0.113.0/24 }
	}
}

Desbloquea una dirección:

sudo nft delete element inet filter blocklist4 { 192.0.2.77 }

Mira qué IP están ahora mismo bajo el limitador de SSH:

sudo nft list set inet filter ssh_meter4

Para borrar una regla concreta necesitas su identificador (handle). Muéstralos con -a:

sudo nft -a list chain inet filter input
		tcp dport { 80, 443 } accept # handle 14
sudo nft delete rule inet filter input handle 14

Para ver cuántos paquetes coinciden con una regla, añade counter delante de la acción en el archivo (por ejemplo tcp dport $WEB_PORTS counter accept) y consúltalo con sudo nft list chain inet filter input.

Paso 7: Consultar el registro

Los paquetes descartados aparecen en el registro del kernel con el prefijo nft-drop::

sudo journalctl -k -g 'nft-drop' -n 20 --no-pager
sep 24 11:10:42 server kernel: nft-drop: IN=eth0 OUT= MAC=... SRC=192.0.2.90 DST=203.0.113.10 LEN=44 PROTO=TCP SPT=40112 DPT=3306 ...

DPT muestra el puerto de destino. Si aparece tráfico legítimo, añade el puerto a las reglas y recarga.

Paso 8 (opcional): Reenviar un puerto con NAT

Si el servidor hace de puerta de enlace para una red privada, por ejemplo una VM en 10.0.0.20, habilita primero el reenvío en el kernel:

echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ip-forward.conf
sudo sysctl --system

Añade al final de /etc/nftables.conf una tabla de NAT. Sustituye eth0 por tu interfaz pública:

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

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

Y permite ese tráfico en la cadena forward de la tabla inet filter, cambiando su contenido por:

    chain forward {
        type filter hook forward priority filter; policy drop;
        ct state established,related accept
        ct status dnat accept
        ip saddr 10.0.0.0/24 oifname "eth0" accept
    }

ct status dnat accept deja pasar cualquier conexión que haya sido redirigida por una regla dnat, así no tienes que repetir la IP y el puerto. Valida y recarga con sudo nft -c -f /etc/nftables.conf && sudo systemctl reload nftables.

Migrar reglas desde iptables

El paquete iptables de Ubuntu incluye herramientas para traducir reglas a sintaxis de nftables. Para una regla suelta:

iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
nft 'add rule ip filter INPUT tcp dport 22 ct state new counter accept'

Para un archivo completo guardado con iptables-save:

sudo iptables-save > iptables.rules
iptables-restore-translate -f iptables.rules > translated.nft

Usa el resultado como punto de partida, no como configuración final: la traducción es literal (tablas ip separadas, cadenas en mayúsculas, sin conjuntos). Lo habitual es reescribirlo en una tabla inet como la del paso 3 y, una vez probado, dejar de usar las reglas de iptables.

Solución de problemas

Error: Could not process rule: No such file or directory: la regla hace referencia a una tabla, cadena o conjunto que no existe o está mal escrito. Revisa los nombres con sudo nft list ruleset.

Error: syntax error, unexpected ... al validar: nft indica la línea y marca con ^^^ la posición exacta. Los fallos típicos son un ; que falta tras policy drop o llaves sin cerrar.

IPv6 deja de funcionar tras aplicar el firewall: falta la regla meta l4proto ipv6-icmp accept, sin la cual el servidor no puede resolver vecinos ni recibir anuncios del router.

Las reglas no se cargan tras reiniciar: comprueba systemctl is-enabled nftables y que UFW no esté habilitado. Consulta errores con sudo journalctl -u nftables -b --no-pager.

Te has quedado sin acceso y no programaste la reversión: entra por la consola del proveedor y ejecuta sudo nft flush ruleset. Corrige el archivo antes de volver a cargarlo.

Conclusión

Has creado un firewall con nftables que cubre IPv4 e IPv6 en una sola tabla, con listas de IP gestionables en caliente, límite de conexiones SSH por IP y registro de lo que se descarta, aplicado con reversión automática y cargado en cada arranque. Mantener todo en /etc/nftables.conf hace que el estado del firewall sea fácil de revisar y versionar.

Como siguientes pasos puedes:

  • Integrar Fail2ban con la acción nftables para añadir automáticamente al bloqueo las IP que fallan al autenticarse.
  • Desplegar el mismo /etc/nftables.conf en todos tus servidores con Ansible.
  • Añadir reglas de salida en la cadena output si necesitas restringir a qué destinos puede conectar el servidor.