Guias

Cómo configurar un firewall en Linux con nftables (reemplazo de iptables)

Cómo configurar un firewall en Linux con nftables (reemplazo de iptables)

nftables es el reemplazo moderno de iptables, oficialmente soportado por el kernel Linux desde 2014. Si tu distro es reciente, ya lo trae.

—

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 2-3 horas para el switch a enforcing.
  • Requisitos: Root, kernel moderno, 2-4 semanas en permissive antes de enforcing.
  • Resultado: Sistema con MAC activo, logs en /var/log/audit/audit.log.
  • Troubleshooting: audit2allow, audit2why, setenforce 0 para emergencias.

¿Por qué nftables y no iptables?

Característica iptables nftables
Sintaxis Compleja, una herramienta por familia (iptables, ip6tables, arptables) Unificada
Performance Más lento en reglas complejas Hasta 5x más rápido en muchos casos
Atomicidad No (reglas se aplican 1 por 1) Sí (transacciones atómicas)
Concurrencia Problemas con iptables-restore Mejor manejo
Sets y maps Limitado con ipset Nativo

Recomendación 2026: usa nftables. iptables sigue funcionando pero ya no evoluciona.

—

Verificar si tienes nftables

Antes de tocar nada, confirmá que el binario nft esté disponible y que el kernel tenga soporte (presente desde Linux 3.13). Si tu distro es Ubuntu 20.04+ o Debian 11+ ya viene instalado; en RHEL/Fedora viene vía nftables.


which nft
nft --version
# nftables v1.0.x (Linux)

Si el primer comando devuelve vacío, significa que nft no está en el PATH. En ese caso instalalo (ver bloque siguiente). Si which lo encuentra pero nft --version falla con error de permisos, estás ejecutando sin capabilities de NET_ADMIN — corré con sudo.

Si no lo tienes:


sudo apt install nftables  # Ubuntu/Debian
sudo dnf install nftables  # Fedora/RHEL

Después de instalar, verifica que el servicio systemd esté activo. En muchas distros queda deshabilitado por defecto, lo que significa que las reglas que cargues a mano se pierden al reiniciar. Arrancalo y habilitalo antes de seguir:


sudo systemctl enable --now nftables

Si systemctl status nftables muestra active (exited), está bien: el demonio de nftables solo aplica la config al inicio y después termina, queda monitoreando vía systemd.

—

Sintaxis básica

Familias (address families):

Futuro Legacy (deprecated) Actual
Familia Uso
ip IPv4
ip6 IPv6
inet IPv4 + IPv6 (recomendado)
arp ARP

Tablas (containers de chains):

  • filter (default): packet filtering
  • nat: NAT
  • route: routing
  • mangle: packet alteration

Chains (rules en tablas):

  • input: paquetes destinados al host
  • output: paquetes originados en el host
  • forward: paquetes enrutados a través del host
  • prerouting / postrouting: NAT

—

Primera configuración: bloquear todo y abrir SSH

El approach correcto es “default deny”: primero tirás todas las reglas que existan (para empezar limpio), después construís una tabla/chains con policy drop que descarta todo por defecto, y solo abres lo que necesitas. Nunca arranques con policy accept y después trates de cerrar puertos — es la receta para olvidarte uno y dejar un hueco.


sudo nft flush ruleset

Este comando borra todas las reglas cargadas en el kernel. Es lo que quieres en este punto, pero cuidado si ya tienes un firewall funcionando en producción: la conexión SSH actual no se cae (las reglas nuevas se aplican en una transacción atómica), pero cualquier servicio que no hayas contemplado quedará bloqueado. Por eso conviene hacer esto en una ventana de mantenimiento o con acceso vía consola (IPMI, KVM, etc.) como backup.

Empezamos con un script básico:


sudo nft add table inet filter
sudo nft add chain inet filter input { type filter hook input priority 0 ; policy drop ; }
sudo nft add rule inet filter input iif lo accept
sudo nft add rule inet filter input ct state established,related accept
sudo nft add rule inet filter input tcp dport 22 accept
sudo nft add rule inet filter input counter drop

Qué hace cada línea:

1. Crea tabla filter familia inet (IPv4+IPv6)

2. Crea chain input con policy drop (bloquear todo por default)

3. Permite tráfico desde loopback

4. Permite conexiones establecidas (importante para que SSH no se caiga después)

5. Permite SSH (puerto 22)

6. Cuenta y bloquea el resto

Atención con el orden de las reglas: nftables evalúa de arriba hacia abajo y se queda en la primera que matchea. Si ponés el counter drop antes de las reglas accept, todo se va a dropear. Si ponés tcp dport 22 accept antes de ct state established,related accept, las conexiones SSH nuevas funcionan pero las respuestas a paquetes legítimos del servidor (que son established) caen antes de llegar al kernel stack.

Verificar:


sudo nft list ruleset

Este comando te muestra todas las reglas cargadas en el kernel (no solo las que acabás de agregar, sino cualquier tabla preexistente). Lo que esperas ver: la tabla inet filter, la chain input con policy drop, y las cinco reglas en orden. Si no aparece el counter drop, la cadena sigue con policy accept implícita — revisa la sintaxis del segundo add chain, especialmente el escape del ; (en bash el backslash es obligatorio para que el shell no interprete el ; como separador de comandos).

Output:


table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        tcp dport 22 accept
        counter drop
    }
}

—

Hacer la configuración persistente

Las reglas que agregamos con nft add viven en la memoria del kernel y se pierden al reiniciar o cuando el servicio nftables se reinicia. Para que sobrevivan, hay que escribirlas en un archivo que systemd cargue en cada boot. El path estándar es /etc/nftables.conf y el servicio nftables.service ya lo lee automáticamente.


sudo nano /etc/nftables.conf

El archivo tiene una shebang especial (#!/usr/sbin/nft -f) que le dice al kernel que ese archivo es ejecutable por el binario nft con flag -f (flush + load atómico). Cuando nftables.service arranca, hace nft -f /etc/nftables.conf, que internamente: 1) hace flush del ruleset, 2) carga todo el archivo en una transacción atómica, 3) si algo falla de sintaxis, no aplica nada (rollback automático).


#!/usr/sbin/nft -f
flush ruleset
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        # Permitir loopback
        iif lo accept
        # Permitir conexiones establecidas
        ct state established,related accept
        # Anti-flooding
        tcp dport 22 accept
        tcp dport 80 accept
        tcp dport 443 accept
        # Permitir ping (opcional)
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept
        # Contar y loguear el resto
        log prefix "nftables dropped: " counter drop
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
}

Diferencia con la config anterior: acá la chain output tiene policy accept (todo lo que el servidor envía sale), y la chain forward tiene policy drop (no enruta tráfico entre interfaces, a menos que sea un router/gateway). Si tu servidor hace NAT o es gateway, vas a tener que agregar reglas específicas en forward (más adelante hay un ejemplo de NAT).

Habilitar servicio:


sudo systemctl enable nftables
sudo systemctl restart nftables

enable registra el servicio para que arranque en cada boot; restart aplica la config nueva inmediatamente. Si ya tienes reglas cargadas a mano, restart las va a reemplazar (flush + load). verifica con sudo nft list ruleset que las reglas que esperabas estén ahí. Si el servicio falla al arrancar, corré sudo journalctl -u nftables -n 50 — el error de sintaxis aparece puntual con número de línea.

—

Caso común: web server

Servidor que expone HTTP/HTTPS + SSH. La política sigue siendo drop por defecto; solo permitimos tráfico DNS saliente (necesario para resolver nombres en queries externas como updates de paquetes), SSH, HTTP/HTTPS y ping. nota que no permitimos DNS entrante — si este server no es un resolver DNS público, no hay razón de abrir el puerto 53 a internet (es uno de los vectores de ataque DDoS amplification más explotados).


table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        # Loopback
        iif lo accept
        # Established
        ct state established,related accept
        # SSH
        tcp dport 22 accept
        # HTTP/HTTPS
        tcp dport { 80, 443 } accept
        # Ping
        ip protocol icmp accept
        # Log + drop resto
        log prefix "dropped: " counter drop
    }
}

Advertencia sobre log prefix: el logging es útil para debugging pero tiene costo. Cada paquete dropeado genera una entrada en el ring buffer del kernel (visible con journalctl -k o dmesg). Bajo un ataque DDoS con miles de paquetes por segundo, el log crece hasta llenar el disco y degrada performance. En producción real, dejá log solo en una regla temporal mientras investigás, y después quítala o usa un rate limit explícito (ver sección de rate limiting más abajo).

—

Caso común: web server + MySQL remoto

Web server habla con DB server. Acá se ve la gracia de nftables: las reglas usan ip saddr para restringir por IP de origen. El DB server no abre MySQL a internet — solo acepta conexiones desde la IP del web server. Es segmentación básica que te ahorra el 90% de los ataques automatizados que prueban puertos de DB.

DB server (10.0.0.10):


table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        # SSH (solo desde tu red de admins)
        ip saddr 203.0.113.42 tcp dport 22 accept
        # MySQL solo desde web server
        ip saddr 10.0.0.20 tcp dport 3306 accept
        log prefix "dropped: " counter drop
    }
}

Verificación crítica: 203.0.113.42 es la IP de tu admin (casa/oficina). Si usas IP dinámica (tipo CGNAT de ISP argentino), esta regla te puede dejar afuera. Soluciones: 1) usar DNS dinámico (DuckDNS, No-IP) y referenciar por nombre en nftables (sí, soporta ip saddr @set con sets de nombres), 2) abrir SSH a un rango de tu ISP, 3) usar VPN (WireGuard) y restringir SSH solo a la subnet VPN. Acá vale la pena leer la guía de WireGuard más abajo.

Web server (10.0.0.20):


table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        # SSH
        ip saddr 203.0.113.42 tcp dport 22 accept
        # HTTP/HTTPS
        tcp dport { 80, 443 } accept
        log prefix "dropped: " counter drop
    }
    chain output {
        type filter hook output priority 0; policy accept;
        # Bloquear DNS exfiltration
        udp dport != 53 reject
    }
}

La regla udp dport != 53 reject en la chain output es defensa contra exfiltración de datos por DNS tunneling (técnica que usan malware para sacar información cifrada en queries DNS). Si un proceso del servidor intenta mandar un paquete UDP saliente que no sea DNS (puerto 53), se rechaza con reject (RST/ICMP) en vez de dropear silenciosamente. Si tienes un resolver local que habla por TCP o por otro puerto, esta regla lo va a romper — ajustala a tus puertos reales.

—

Rate limiting con nftables

Limitar requests por IP para prevenir DDoS. nftables tiene meters nativos (objetos en el kernel que cuentan eventos por intervalo de tiempo). Esto es distinto al rate limiting de iptables legacy, que era más lento y limitado. Los meters viven en una tabla aparte (meter ratemeter) y las reglas los invocan; si excedés el rate, el paquete se descarta automáticamente.


table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        # SSH con rate limit
        tcp dport 22 accept
        # HTTP rate limit: 25 requests/segundo por IP
        tcp dport 80 meter ratemeter { ip saddr limit rate 25/second } accept
        # HTTPS con rate limit
        tcp dport 443 meter ratemeter { ip saddr limit rate 25/second } accept
        log prefix "dropped: " counter drop
    }
}

Cómo elegir el rate: 25/second significa 25 paquetes por segundo por IP de origen. Si tu web sirve HTMLs de ~50 KB y un usuario legítimo navega normal (digamos 5 requests/sec), no llega al límite. Pero un bot que scrapea agresivamente o un DDoS con miles de conexiones se topa con el límite en seguida. Para apps API con tráfico más intenso, subí a 100/second o 1000/minute. Para SSH, la regla accept sin meter está bien — SSH legítimo es muy bajo rate y los bots prueban miles de credenciales, eso lo cubre fail2ban (no nftables).

—

NAT con nftables

Si tu servidor hace NAT (gateway), necesitas la tabla nat (no filter) con chains prerouting y postrouting. La diferencia con filter: acá reescribís direcciones (DNAT/SNAT) en vez de descartar paquetes. El kernel usa la connection tracking table (conntrack) para mantener la relación entre el paquete original y el reescrito.


table inet nat {
    chain prerouting {
        type nat hook prerouting priority -100; policy accept;
        # Redirigir puerto 80 a servidor interno 10.0.0.20
        tcp dport 80 dnat to 10.0.0.20
    }
    chain postrouting {
        type nat hook postrouting priority 100; policy accept;
        # Source NAT para tráfico saliente
        oifname "eth0" snat to 203.0.113.10
    }
}

Dos errores comunes acá: 1) la prioridad en NAT es negativa (-100 para prerouting) para que se ejecute antes que las reglas de filter, así un paquete que se va a DNAT ya tiene la dirección de destino cambiada cuando llega al filter chain. 2) El dnat to acepta un IP solo, o un rango con notación ip1-ip2. Si quieres balancear entre varios backends, agregás dos reglas dnat to con distintas IPs y una regla de random arriba (numgen random mod 2 eq 0). Para algo más serio (health check), ya conviene HAProxy o un load balancer dedicado.

No olvides: para que el forwarding funcione, además de las reglas NAT, tienes que tener net.ipv4.ip_forward=1 en /etc/sysctl.conf (revisable con sysctl net.ipv4.ip_forward). Sin eso, los paquetes llegan al kernel pero nunca salen por la otra interfaz.

—

Sets y maps: reglas para muchas IPs

Si tienes 100 IPs a banear, escribir 100 reglas ip saddr X.X.X.X drop es lento de cargar, ocupa memoria, y al kernel le cuesta evaluar muchas reglas. Los sets son estructuras de datos optimizadas (hash tables o tries según el tamaño) que permiten agrupar elementos y matchear contra ellos en O(1). Mucho más eficiente.


# Crear set
table inet filter {
    set banned_ips {
        type ipv4_addr
        elements = { 1.2.3.4, 5.6.7.8, 9.10.11.12 }
    }
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        # Bloquear IPs en set banned_ips
        ip saddr @banned_ips drop
        tcp dport 22 accept
        tcp dport { 80, 443 } accept
        log prefix "dropped: " counter drop
    }
}

Añadir/quitar IPs dinámicamente:


sudo nft add element inet filter banned_ips { 13.14.15.16 }
sudo nft delete element inet filter banned_ips { 13.14.15.16 }

Estos comandos modifican el set sin flush ni restart del firewall. La nueva IP empieza a ser bloqueada (o deja de estarlo) instantáneamente. Por eso los sets son ideales para integrar con fail2ban o scripts automáticos que detectan abuso. Si el set tiene que sobrevivir reinicios, agregalo al /etc/nftables.conf dentro del bloque table inet filter.

—

Migrar desde iptables

Si tienes reglas iptables existentes, nft tiene un traductor. La forma incorrecta (que mucha gente prueba primero): hacer iptables-save y tratar de cargar el dump con nft -f. No funciona porque el formato es completamente distinto.


# Convertir reglas iptables a nftables
sudo iptables-save > /tmp/iptables-rules.txt
sudo nft -f /tmp/iptables-rules.txt  # No convierte directamente, pero...

Lo correcto es usar iptables-translate, una utilidad que viene con el paquete nftables y traduce regla por regla la sintaxis iptables a nftables. No es 100% perfecto (algunos match extensions muy específicos no se traducen), pero para el 95% de las reglas comunes (filter por IP, por puerto, por interfaz, conntrack, NAT básico) anda perfecto.

Mejor: usar iptables-translate que viene con nft:


sudo iptables-translate -f /etc/iptables/rules.v4

El flag -f lee un archivo de reglas en formato iptables-save y emite por stdout la versión nft equivalente. Output:


nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport 80 accept

Atención: iptables-translate traduce la sintaxis, pero no la ejecuta. Te queda un script que puedes revisar, ajustar, y correr después. Antes de migrar, prueba en un server de testing — si tienes reglas custom con módulos xtables raros (geoip, recent, condition), la traducción puede no funcionar y quedarte con reglas incompletas. Mantené el firewall viejo activo hasta validar que el nuevo hace exactamente lo mismo.

—

Logs y debugging

Ver contadores de reglas. El flag -a muestra el handle de cada regla (un identificador numérico interno). Sirve para borrar reglas específicas con nft delete rule ... handle X cuando ya sabes qué número tiene.


sudo nft list ruleset -a

Si una regla tiene un counter alto (millones de paquetes), es candidata a optimizarse (mover más arriba en la chain para que matchee antes). Si tiene counter cero, probablemente está mal escrita o el tráfico no llega.

Ver logs de paquetes bloqueados:


sudo journalctl -k | grep nftables
sudo tail -f /var/log/kern.log | grep nftables

journalctl -k muestra los mensajes del kernel (donde nftables loguea con log prefix). El prefijo que pusiste ("nftables dropped: ", "dropped: ") aparece antes de cada mensaje, así que filtrar con grep te da exactamente los drops. Bajo ataque, el volumen de logs puede tirar abajo syslog — pasale --rate 10/second al log para limitar (limita cuántas veces por segundo loguea el prefijo).

—

Monitorear ataques en tiempo real


# Ver IPs que más paquetes generan
sudo nft list set inet filter banned_ips 2>/dev/null
sudo nft list chain inet filter input | grep counter
# Top IPs que más tráfico generan
sudo tcpdump -i any -n -q | head

El primer grep counter te muestra el contador acumulado de cada regla que tenga counter aplicado — útil para ver si la regla del SSH está matcheando (probablemente sí) o si la regla de DROP final está sumando paquetes (probablemente sí, si hay ataque). El tcpdump con -q (quick) te da headers resumidos, no payload — bajo ataque quieres ver origen/destino/puerto sin ahogar la consola. Si ves un patrón (mismo origen miles de veces), agregalo al set banned_ips con nft add element.

—

Migrar desde ufw

Si usas UFW (Ubuntu Firewall), desactívalo primero. UFW genera reglas iptables legacy por debajo; si lo dejás activo mientras cargás nftables, los dos stack compiten y se pisan las reglas. El resultado: comportamiento impredecible.


sudo ufw disable
sudo systemctl stop ufw
sudo systemctl disable ufw

ufw disable desactiva las reglas en runtime; systemctl stop apaga el demonio; disable evita que arranque en el próximo boot. Después de los tres, verifica con sudo iptables -L que las chains de UFW estén vacías. Si quedaron reglas residuales (a veces pasa si deshabilitas ufw pero no reinicias el servicio iptables), corré sudo iptables -F && sudo iptables -t nat -F para flushear todo manualmente.

Luego configura nftables como arriba.

—

Resumen

nftables es el futuro de firewalling en Linux. Setup básico toma 5 minutos:

1. Crear tabla y chain con policy drop

2. Permitir loopback y established

3. Permitir puertos específicos

4. Guardar en /etc/nftables.conf

5. systemctl enable nftables

Si tienes un servidor expuesto a internet, no te quedes sin firewall. 5 minutos de setup te pueden ahorrar años de problemas.

Lee también: Fail2ban para SSH (capa adicional contra fuerza bruta), o Hardening de SSH para asegurar el acceso remoto.


Preguntas frecuentes

¿Por qué Cómo configurar un firewall en Linux con nftables (reemplazo de iptables) es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con Cómo configurar un firewall en?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con Cómo configurar un firewall en?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar Cómo configurar un firewall en y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a Cómo configurar un firewall en?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

4 thoughts on “Cómo configurar un firewall en Linux con nftables (reemplazo de iptables)”

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

bridge Bridging