Hasta la fecha, he estado utilizando ufw como firewall en los distintos servidores que he ido levantando. Pero llega un momento, en que quieres dar un paso mas y probar, y como bien sabes yo soy de esos de probar. Así, que en lugar de seguir con ufw, he decido dar el salto, y cambiar definitivamente a nftables. En el capítulo anterior cerraste la puerta de entrada con llaves Ed25519, cambiaste la cerradura (puerto 8822) y pusiste candados en PermitRootLogin y AllowUsers. Tu SSH está más seguro que el cofre del banco de España. Pero hay un problema, la puerta sigue siendo de madera. Es necesario aumentar los obstáculos y poner las cosas un poco mas difíciles. Necesitas un firewall. Y no uno cualquiera, necesitas nftables.
¿Por qué nftables y no iptables?
Vamos a ser sinceros. Si has tocado Linux en los últimos quince años, conoces iptables. Es el firewall clásico, el de toda la vida, el que aparece en todos los tutoriales de 2010. Pero iptables está en mantas desde 2014. La comunidad lleva más de una década moviéndose a nftables, y en Ubuntu 24.04 iptables es una capa de compatibilidad sobre nftables. No tiene sentido aprender el envoltorio cuando puedes ir directo al núcleo. Las diferencias clave son tres,
- Rendimiento. nftables evalúa reglas en espacio de kernel con un bytecode optimizado. No hay recorrido lineal de cadenas como en iptables. Esto se nota cuando tienes cientos de reglas o tu servidor recibe tráfico intenso.
- Sintaxis más limpia. Olvídate de los crípticos
-A INPUT -p tcp --dport 22 -j ACCEPT. En nftables escribestcp dport 22 accept. Es casi lenguaje natural. La sintaxis es homogénea, los mismos conceptos se aplican a IPv4, IPv6, ARP, bridge y netdev. No necesitas aprender cuatro herramientas diferentes. - IPv4 e IPv6 en una sola tabla. Con iptables tenías que mantener dos conjuntos de reglas paralelos, iptables para IPv4 e ip6tables para IPv6. Un desastre. Con nftables usas la familia
inety tus reglas aplican a ambos protocolos simultáneamente. Menos archivos, menos errores, menos trabajo.
Además, nftables trae características que iptables nunca tuvo, conjuntos y mapas nativos, rate limiting integrado, logging estructurado, y la capacidad de actualizar reglas en caliente sin reiniciar el firewall.
Piensa en iptables como un martillo. Funciona, pero para todo. nftables es una caja de herramientas completa, tienes el martillo, pero también el destornillador, la llave inglesa y el nivel.
Instalación y activación
Ubuntu 24.04 ya incluye nftables en el sistema base. Pero viene desactivado por defecto. Vamos a activarlo.
sudo apt update
sudo apt install nftables -y
El paquete nftables instala el binario nft, el servicio systemd y un archivo de configuración base en /etc/nftables.conf. Actívalo,
sudo systemctl enable nftables
sudo systemctl start nftables
Comprueba que el servicio está corriendo,
sudo systemctl status nftables
Deberías ver algo como active (running). Si ves dead o inactive, algo no va bien. No te preocupes, lo solucionaremos en la sección de troubleshooting.
Un detalle importante, cuando activas nftables por primera vez, no deberías perder la conexión si lo haces en local. Pero si estás en un servidor remoto (DigitalOcean, Hetzner, un VPS cualquiera), no actives el servicio sin antes tener tus reglas cargadas. Te adelanto el problema, por defecto nftables aplica una política restrictive. Si activas el servicio sin reglas que permitan tu SSH en el puerto 8822, te quedas fuera.
¿La solución? Escribe tus reglas primero, cárgalas con nft -f /etc/nftables.conf, y solo entonces activa el servicio. Lo veremos en detalle más abajo.
Conceptos básicos, tablas, cadenas y reglas
nftables se organiza en tres niveles. Piensa en ello como un sistema de cajas etiquetadas.
Tablas
Una tabla es el contenedor principal. Agrupa cadenas y reglas que trabajan sobre el mismo tipo de tráfico. Cada tabla pertenece a una familia de direcciones,
- inet: IPv4 e IPv6 juntos. Es la que usarás siempre.
- ip: solo IPv4.
- ip6: solo IPv6.
- arp: tráfico ARP.
- bridge: tráfico de puentes.
- netdev: tráfico en interfaces de red (para ruteo temprano, avanzado).
Para un servidor auto-hospedado normal, una sola tabla de familia inet es suficiente.
table inet filter {
# aquí van cadenas y reglas
}
Cadenas
Las cadenas definen dónde se evalúa el tráfico dentro del stack de red. Son los puntos de enganche. Las nftables llama «base chains» y se asocian a hooks del kernel,
- prerouting: antes de la decisión de ruteo.
- input: paquetes destinados al servidor local.
- forward: paquetes que el servidor reenvía.
- output: paquetes generados por el servidor.
- postrouting: después de la decisión de ruteo.
Para proteger tu servidor, las cadenas que te interesan son input y output. Forward solo si actúas como router.
chain input {
type filter hook input priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
Reglas
Las reglas son las instrucciones dentro de cada cadena. Tienen el formato,
<tipo de paquete> <condición> <acción>
Por ejemplo,
tcp dport 22 accept
Permite tráfico TCP hacia el puerto 22. Sencillo, directo, sin ruido.
Policy
La política es la acción por defecto cuando un paquete no coincide con ninguna regla. policy drop significa «cierra todo por defecto». policy accept significa «deja pasar todo por defecto». La filosofía de seguridad es clara, cierra todo, abre solo lo necesario.
Configuración stateful firewall
Vamos a construir la configuración paso a paso. El resultado final será un archivo /etc/nftables.conf que puedes copiar y pegar en tu servidor.
Política drop por defecto
La base de cualquier firewall serio. Si no está explícitamente permitido, está prohibido.
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Forward lo dejamos en drop porque nuestro servidor no va a actuar como router (a menos que tengas una topología más compleja). Output en accept porque confiamos en las peticiones que hace nuestro servidor. Si quieres ser más restrictivo, pon output en drop y permite explícitamente las salidas a DNS, HTTP, HTTPS y tu repositorio APT.
Permitir loopback
La interfaz loopback (lo) es tráfico interno del servidor. Muchos servicios y aplicaciones del sistema usan loopback para comunicarse. Bloquearlo rompe cosas.
chain input {
type filter hook input priority 0; policy drop;
# Loopback, confianza total
iif "lo" accept;
}
Permitir conexiones establecidas y relacionadas
Este es el truco que separa un firewall funcional de uno que te da dolores de cabeza. No necesitas permitir cada puerto de salida individualmente. Basta con permitir las respuestas a conexiones que tu servidor inició.
ct state related,established accept
Esta línea es mágica. Cuando tu servidor hace una petición HTTP a Google, Google responde. Esa respuesta entra por input y coincide con ct state established. Cuando haces un ping, el echo reply coincide con ct state related. Una regla, todo cubierto.
Permitir SSH (puerto 8822)
Del capítulo 1, tienes SSH corriendo en el puerto 8822. Necesitas permitirlo explícitamente.
tcp dport 8822 accept
Pon esta regla justo encima del drop por defecto, después de las reglas de loopback y established/related.
Si quieres ser más restrictivo, puedes limitar el origen a tu IP,
tcp dport 8822 ip saddr 192.168.1.100 accept
Pero si tienes IP dinámica en casa, esto te dejará fuera cuando cambie. Una alternativa es usar una lista de IPs permitidas con conjuntos en nftables, pero para este capítulo mantenemos la regla abierta al mundo y confiamos en la autenticación por clave del capítulo 1.
HTTP y HTTPS
Si tu servidor aloja servicios web accesibles desde internet, necesitas abrir los puertos 80 y 443.
tcp dport {http, https} accept
La sintaxis de conjunto {80, 443} funciona igual. Usa los nombres de servicio definidos en /etc/services o los números directamente, como prefieras.
Si no tienes servicios web, no abras estos puertos. Cada puerto abierto es una superficie de ataque potencial.
ICMP controlado
Hay gente que bloquea todo el ICMP. Es un error. ICMP es necesario para el funcionamiento de la red, Path MTU Discovery, detección de destinos inalcanzables, gestión de congestión. Bloquear todo ICMP causa problemas difíciles de diagnosticar.
Lo correcto es permitir ICMP de forma controlada,
# ICMP necesario para el funcionamiento de la red
icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem } accept
icmpv6 type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
Para IPv6 los tipos necesarios son más porque el protocolo usa ICMPv6 para funciones que en IPv4 hacía ARP (NDP). Si bloqueas ICMPv6 indiscriminadamente, tu servidor no podrá resolver vecinos IPv6. Y sí, aunque no uses IPv6 externamente, tu servidor tiene link-local IPv6 activo por defecto.
Rate limiting, protección contra escaneos
Los bots escanean internet constantemente. En cuanto tu servidor tenga una IP pública, recibirás conexiones a puertos aleatorios. Es ruido de fondo.
Puedes reducir drásticamente ese ruido con rate limiting. La idea es sencilla, si una IP hace demasiadas conexiones en poco tiempo, empiezas a descartar sus paquetes.
# Rate limiting, máximo 10 conexiones por segundo por IP
tcp flags syn tcp dport 8822 \
meter flood-dev ssh { ip saddr limit rate 10/second } accept
Este medidor (meter) crea una tabla dinámica que rastrea la tasa de conexiones por IP. Si una IP supera las 10 conexiones por segundo, el paquete no coincide y cae en la política drop.
Puedes aplicar rate limiting a todos los puertos, no solo SSH,
# Rate limiting genérico para conexiones nuevas
tcp flags syn \
meter flood-gen { ip saddr limit rate 5/second burst 10 } accept
El parámetro burst permite ráfagas cortas. 5 conexiones por segundo con burst de 10 significa que puedes tener 10 conexiones instantáneas y luego se estabiliza a 5 por segundo.
Un ajuste fino, combina rate limiting con logging para detectar ataques sin cegarte con logs.
tcp flags syn \
meter flood-log { ip saddr limit rate 5/second } \
log prefix "RATE-LIMIT: " accept
Persistencia de reglas
Has escrito tus reglas, las has probado, funcionan. Ahora tienes que hacer que sobrevivan a un reinicio. nftables no guarda las reglas automáticamente. Tienes que volcarlas a un archivo.
sudo nft list ruleset > /etc/nftables.conf
Este comando exporta todas las reglas activas en memoria al archivo de configuración. systemd cargará este archivo al arrancar el servicio.
Pero espera, el formato de nft list ruleset no es exactamente el mismo que el formato que nftables espera al cargar. Especificamente, nft list añade counter a cada regla para mostrar estadísticas. Si exportas así y luego cargas, las reglas funcionan, pero los contadores se reinician cada vez que reinicias el servicio.
Para una exportación limpia, usa,
sudo nft -o list ruleset > /etc/nftables.conf
La opción -o (optimize) produce una salida más limpia. Aún así, es mejor escribir el archivo a mano y luego cargarlo. Así tienes control total sobre el contenido y puedes añadir comentarios.
Script completo listo para producción
Aquí tienes el archivo completo. Cópialo, ajústalo a tus necesidades y desplégalo.
#!/usr/sbin/nft -f
# /etc/nftables.conf
# Firewall stateful para servidor auto-hospedado
# Ubuntu 24.04 - Puertos personalizados
flush ruleset
table inet filter {
# Cadena de entrada
chain input {
type filter hook input priority 0; policy drop;
# Loopback: necesario para servicios locales
iif "lo" accept
# Conexiones establecidas y relacionadas
ct state related,established accept
# SSH en puerto personalizado (del capítulo 1)
tcp dport 8822 accept
# Servicios web opcionales
# tcp dport {http, https} accept
# ICMP controlado para IPv4
icmp type {
echo-request,
echo-reply,
destination-unreachable,
time-exceeded,
parameter-problem
} accept
# ICMP controlado para IPv6
icmpv6 type {
echo-request,
echo-reply,
destination-unreachable,
time-exceeded,
parameter-problem,
nd-router-solicit,
nd-router-advert,
nd-neighbor-solicit,
nd-neighbor-advert
} accept
# Rate limiting para conexiones SYN
tcp flags syn \
meter flood { ip saddr limit rate 10/second burst 20 } \
log prefix "NF-RATE-LIMIT: " accept
# Logging para tráfico descartado
log prefix "NF-DROP: " limit rate 5/minute
}
# Cadena de reenvío (cerrada por defecto)
chain forward {
type filter hook forward priority 0; policy drop;
}
# Cadena de salida (abierta por defecto)
chain output {
type filter hook output priority 0; policy accept;
}
}
Algunas notas sobre este script,
flush rulesetlimpia todas las reglas existentes antes de cargar las nuevas. Es importante para evitar duplicados si ejecutas el archivo varias veces.- El logging de descartes está limitado a 5 mensajes por minuto. Sin ese límite, un escaneo de puertos llenaría tu syslog en segundos.
- Los puertos HTTP/HTTPS están comentados. Descoméntalos si tu servidor tiene servicios web.
- No hay reglas para mDNS, DHCP, UPnP u otros protocolos. Si necesitas alguno, añádelo.
Carga el archivo con,
sudo nft -f /etc/nftables.conf
Ninguna salida significa que todo fue bien. Si hay errores, nftables te dirá la línea exacta y el problema.
Verificación
Has cargado tus reglas. Ahora toca comprobar que todo funciona como esperas.
Listar todas las reglas activas
sudo nft list ruleset
Este comando muestra todas las tablas, cadenas y reglas cargadas actualmente en memoria. Deberías ver exactamente lo que pusiste en tu archivo, con contadores en cero o con valores si ya ha pasado tráfico.
Ver estadísticas de reglas
Añade la opción -a para ver handles (identificadores numéricos de cada regla),
sudo nft -a list ruleset
Los handles son necesarios si luego quieres eliminar reglas específicas con nft delete rule.
Comprobar conectividad SSH
Desde otra terminal (o desde otro equipo),
ssh -p 8822 usuario@tu-servidor
Si funciona, el firewall está dejando pasar las conexiones SSH. Si no funciona, revisa,
- Que el puerto en la regla coincide con tu configuración de SSH.
- Que la regla está antes del drop por defecto.
- Que no hay un rate limit demasiado agresivo.
Ver paquetes descartados
Los contadores de la política drop se acumulan con cada paquete bloqueado.
sudo nft list chain inet filter input | grep "policy drop"
Verás algo como policy drop; counter 147 packets, 12345 bytes. El contador sube con cada paquete bloqueado. Si ves números altos, tu servidor está recibiendo tráfico no deseado (normal en internet).
Probar ICMP
ping -c 3 tu-servidor
Deberías recibir respuestas. Si no llegan, revisa que los tipos ICMP están en tu configuración. Algunos proveedores de VPS bloquean ICMP a nivel de hipervisor, independientemente de tu firewall. En ese caso, no es problema tuyo.
Probar puertos cerrados
nmap -p 1-1000 tu-servidor
Con la configuración stateful, los puertos no abiertos deberían aparecer como filtered (sin respuesta) o closed (rechazados, aunque con policy drop deberían ser filtered). No deberías ver puertos open que no hayas permitido.
Troubleshooting, cómo no quedarse fuera
El miedo número uno al configurar un firewall es quedarse fuera del servidor. Es legítimo. Una regla mal escrita y tu conexión SSH muere. Aquí tienes un plan de contingencia.
Conexión de rescate
Antes de tocar el firewall en un servidor remoto, asegúrate de tener,
- Consola out-of-band. Todos los proveedores de VPS serios ofrecen una consola web (VNC, serial, IPMI). Aprende a acceder a ella antes de necesitarla.
- Segunda sesión SSH abierta. Nunca cierres la sesión actual hasta que la nueva configuración esté probada. Abre una segunda conexión SSH, carga las nuevas reglas, y si la segunda sesión funciona, entonces cierra la primera.
- Temporizador. Usa
ato un script consleeppara revertir los cambios automáticamente si pierdes la conexión. Algo así,
sudo nft -f /etc/nftables.conf && \
(sleep 60 && sudo systemctl stop nftables) &
Esto carga las reglas y programa la parada del firewall en 60 segundos. Si pierdes la conexión, en un minuto el firewall se para y puedes volver a entrar. Si todo va bien, ejecuta kill %1 para cancelar el temporizador.
Depuración con nft monitor
Cuando algo no funciona como esperas, nft monitor es tu mejor amigo. Muestra todos los eventos del firewall en tiempo real.
sudo nft monitor
Verás cada paquete que atraviesa las reglas, con información detallada. Es como tener un radar para tu tráfico de red.
Si solo te interesa el tráfico descartado,
sudo nft monitor | grep "DROP"
Combínalo con tu regla de logging para ver exactamente qué paquetes están siendo bloqueados y por qué.
Errores comunes
«iptables/ip6tables: command not found»
En Ubuntu 24.04, iptables ya no viene instalado por defecto. Usa nftables directamente. Si necesitas iptables por compatibilidad, instala iptables-nftables-compat.
«Error: Could not process rule: File exists»
Estás intentando crear una tabla o cadena que ya existe. Añade flush ruleset al principio de tu archivo o usa nft delete table para limpiar primero.
«Error: syntax error, unexpected string»
Revisa los puntos y comas. En nftables, cada regla termina con punto y coma. Es fácil olvidar uno, sobre todo en bloques con llaves.
El firewall se para después de un reinicio
systemd no está cargando el archivo al arrancar. Comprueba que el servicio está habilitado:
sudo systemctl enable nftables
Y que el archivo /etc/nftables.conf tiene las reglas correctas.
Rate limiting demasiado agresivo
Si te quedas fuera de SSH porque el rate limiting te bloquea, el problema suele ser que estás usando un cliente que abre múltiples conexiones en paralelo. Aumenta el burst o el límite por segundo. 10/second con burst 20 suele ser suficiente para humanos.
El servidor no resuelve DNS
Revisa que la cadena output está en accept. Si la pusiste en drop, necesitas permitir explícitamente UDP al puerto 53 de tu servidor DNS.
udp dport 53 accept
Próximos pasos
Con nftables tienes un firewall stateful que protege tu servidor contra tráfico no deseado, escaneos de puertos y conexiones brutas. Cada puerto cerrado es un vector de ataque eliminado.
En el próximo capítulo instalaremos y configuraremos Tailscale. Porque no todo es cerrar puertos – a veces la solución es que ciertos servicios ni siquiera estén expuestos a internet.
Verificación final
Resumen de comandos para comprobar que todo está funcionando,
# Estado del servicio
sudo systemctl status nftables
# Listar todas las reglas activas
sudo nft list ruleset
# Monitorear tráfico en tiempo real
sudo nft monitor
# Ver estadísticas de paquetes descartados
sudo nft list chain inet filter input | grep -c "policy drop"
# Probar conexión SSH
ssh -p 8822 usuario@localhost
# Probar ICMP
ping -c 3 localhost
# Cargar reglas desde archivo
sudo nft -f /etc/nftables.conf