Home Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other All categories Projects About Tools

Practical guide to iptables and nftables in 2026

Leer en espanol
Practical guide to iptables and nftables in 2026

Table of contents

In 2026, the Linux packet filtering ecosystem is experiencing a transition that has been underway for years but that many teams continue to postpone: the step of iptables to nftables. Distributions like Debian 10+, RHEL 8+, Ubuntu 20.04+ and Arch Linux already use nftables as a backend by default. However, iptables is still present in thousands of production servers, deployment scripts and documentation that no one dares to touch.

This guide doesn't choose sides: you'll learn both tools, understand when to use each, and know how to migrate when the time comes. If you manage Linux servers or work in network security, here's the handy reference you need to have on hand.

Fundamental concepts

Both iptables and nftables are user interfaces for netfilter, the packet filtering framework built into the Linux kernel. Netfilter operates at several points along the path of a packet (hooks): PREROUTING, INPUT, FORWARD, OUTPUT and POSTROUTING.

Tables and chains

The rules are organized into tables according to its function:

  • filter — The default table. It contains the INPUT, FORWARD and OUTPUT chains to decide whether a packet is accepted or discarded.
  • Nat — For address translation (PREROUTING, OUTPUT, POSTROUTING).
  • mangrove — To modify packet headers.
  • raw — For connection tracking control (conntrack).

Each table contains chains (chains), which are ordered lists of rules. When a packet traverses a chain, the rules are evaluated in order until one matches. If none match, the default policy of the chain.

A ruler It has two parts: the match criteria and the target. The most common targets are ACCEPT, DROP, REJECT, LOG and RETURN.


iptables: Quick Reference Guide

Clear and reset rules

The first step before setting up a clean firewall is to remove all existing rules:

BASH
Bash
# Eliminar todas las reglas de la tabla filter
iptables --flush
iptables --delete-chain

# Eliminar todas las reglas de NAT y mangle
iptables --table nat --flush
iptables --table mangle --flush

# Resetear contadores de paquetes y bytes
iptables --zero

Default policies

The default policy determines what happens when no rules match. The safest strategy is deny all by default and then allow only what is necessary:

BASH
Bash
# Política restrictiva (recomendada para servidores)
iptables --policy INPUT DROP
iptables --policy FORWARD DROP
iptables --policy OUTPUT ACCEPT

# Política permisiva (útil durante desarrollo)
iptables --policy INPUT ACCEPT
iptables --policy FORWARD ACCEPT
iptables --policy OUTPUT ACCEPT

View active rules

BASH
Bash
# Listar reglas con números de línea
iptables --list --line-numbers

# Verbose: muestra interfaz, contadores de paquetes/bytes y opciones
iptables --list --verbose --numeric --line-numbers

# Ver solo la tabla nat
iptables --table nat --list --verbose --numeric --line-numbers

Delete rules

BASH
Bash
# Eliminar por número de línea (ver con --line-numbers primero)
iptables --delete INPUT 3

# Eliminar por coincidencia exacta con la regla
iptables --delete INPUT --protocol tcp --dport 8080 --jump ACCEPT

Save and restore rules

iptables rules do not persist after a reboot. To make them permanent:

BASH
Bash
# Guardar reglas actuales
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

# Restaurar reglas desde archivo
iptables-restore < /etc/iptables/rules.v4

In systems with systemd, install the package iptables-persistent (Debian/Ubuntu) or enable the service iptables (RHEL/CentOS) so that the rules are automatically loaded at boot.

Usual rules

BASH
Bash
# Permitir loopback (imprescindible)
iptables --append INPUT --in-interface lo --jump ACCEPT
iptables --append OUTPUT --out-interface lo --jump ACCEPT

# Permitir conexiones ya establecidas y relacionadas
iptables --append INPUT --match conntrack --ctstate ESTABLISHED,RELATED --jump ACCEPT

# Descartar paquetes con estado INVALID
iptables --append INPUT --match conntrack --ctstate INVALID --jump DROP

# SSH (puerto 22)
iptables --append INPUT --protocol tcp --dport 22 --match conntrack --ctstate NEW,ESTABLISHED --jump ACCEPT

# HTTP y HTTPS
iptables --append INPUT --protocol tcp --match multiport --dports 80,443 --jump ACCEPT

# DNS (si el servidor actúa como resolver)
iptables --append INPUT --protocol udp --dport 53 --jump ACCEPT
iptables --append INPUT --protocol tcp --dport 53 --jump ACCEPT

# ICMP (ping) -- permite diagnóstico de red
iptables --append INPUT --protocol icmp --icmp-type echo-request --jump ACCEPT

Block traffic by IP, range and port

BASH
Bash
# Bloquear una IP concreta
iptables --append INPUT --source 203.0.113.42 --jump DROP

# Bloquear un rango CIDR
iptables --append INPUT --source 198.51.100.0/24 --jump DROP

# Bloquear rango de puertos
iptables --append INPUT --protocol tcp --dport 8000:8100 --jump DROP

# Bloquear una IP de origen en un puerto específico
iptables --append INPUT --source 203.0.113.42 --protocol tcp --dport 80 --jump DROP

Rate limiting (DoS prevention)

BASH
Bash
# Limitar nuevas conexiones SSH a 3 por minuto por IP
iptables --append INPUT --protocol tcp --dport 22 \
  --match conntrack --ctstate NEW \
  --match recent --set --name SSH --rsource

iptables --append INPUT --protocol tcp --dport 22 \
  --match conntrack --ctstate NEW \
  --match recent --update --seconds 60 --hitcount 4 --name SSH --rsource \
  --jump DROP

# Limitar SYN floods usando hashlimit
iptables --append INPUT --protocol tcp --syn \
  --match hashlimit \
  --hashlimit-name syn_flood \
  --hashlimit-above 200/second \
  --hashlimit-burst 1000 \
  --hashlimit-mode srcip \
  --jump DROP

NAT and port forwarding

BASH
Bash
# Habilitar IP forwarding (obligatorio para NAT)
echo 1 > /proc/sys/net/ipv4/ip_forward
# O de forma permanente en /etc/sysctl.conf:
# net.ipv4.ip_forward = 1

# MASQUERADE -- NAT de salida para una interfaz (p.ej. router con IP dinámica)
iptables --table nat --append POSTROUTING --out-interface eth0 --jump MASQUERADE

# SNAT -- NAT de salida con IP estática (más eficiente que MASQUERADE)
iptables --table nat --append POSTROUTING --source 192.168.1.0/24 \
  --out-interface eth0 --jump SNAT --to-source 203.0.113.10

# DNAT -- Reenvío de puerto 80 externo a servidor interno en 8080
iptables --table nat --append PREROUTING \
  --in-interface eth0 --protocol tcp --dport 80 \
  --jump DNAT --to-destination 192.168.1.100:8080

# Permitir el tráfico redirigido en FORWARD
iptables --append FORWARD --destination 192.168.1.100 --protocol tcp \
  --dport 8080 --jump ACCEPT

Logging

BASH
Bash
# Registrar paquetes descartados (insertar ANTES de la regla DROP)
iptables --append INPUT --match limit --limit 5/min --jump LOG \
  --log-prefix "iptables-DROP: " --log-level 4

# Registrar intentos de conexión a puertos no permitidos
iptables --append INPUT --protocol tcp --dport 23 --jump LOG \
  --log-prefix "TELNET-BLOCKED: " --log-level warning

# Los logs aparecen en /var/log/kern.log o con: journalctl -k | grep iptables

nftables: The evolution of filtering in Linux

Why nftables in 2026

nftables is not just "iptables with better syntax". Provides important structural improvements:

  • Unified Framework- Replaces iptables, ip6tables, arptables and ebtables with a single tool.
  • Atomic operations- You can load an entire set of rules in a single operation, with no unprotected time windows.
  • Sets and maps: Native data structures to manage lists of IPs, ports or ranges efficiently.
  • Better performance- The generated bytecode is more efficient than the linear evaluation model of iptables.
  • Readable syntax- Rules are more compact and easier to audit.

Basic syntax

The hierarchy in nftables is: family → table → chain → rule. The most common families are ip (IPv4), ip6 (IPv6), inet (both), arp and bridge.

BASH
text
# Crear una tabla
nft add table inet filter

# Crear una cadena con política por defecto DROP
nft add chain inet filter input \
  '{ type filter hook input priority 0 ; policy drop ; }'

# Crear la cadena OUTPUT con política ACCEPT
nft add chain inet filter output \
  '{ type filter hook output priority 0 ; policy accept ; }'

# Añadir una regla (permite SSH)
nft add rule inet filter input tcp dport 22 ct state new,established accept

# Listar todo el ruleset
nft list ruleset

# Listar una tabla concreta
nft list table inet filter

Equivalences with iptables

The most common rules have their direct equivalent in nftables, with a more natural syntax:

BASH
text
# Loopback
nft add rule inet filter input iif lo accept
nft add rule inet filter output oif lo accept

# Conexiones establecidas y relacionadas
nft add rule inet filter input ct state established,related accept

# Descartar paquetes INVALID
nft add rule inet filter input ct state invalid drop

# SSH, HTTP y HTTPS en una sola regla
nft add rule inet filter input tcp dport { 22, 80, 443 } accept

# DNS
nft add rule inet filter input udp dport 53 accept
nft add rule inet filter input tcp dport 53 accept

# ICMP (ping)
nft add rule inet filter input icmp type echo-request accept
nft add rule inet filter input icmpv6 type echo-request accept

# Bloquear una IP
nft add rule inet filter input ip saddr 203.0.113.42 drop

# Bloquear un rango CIDR
nft add rule inet filter input ip saddr 198.51.100.0/24 drop

# Rango de puertos
nft add rule inet filter input tcp dport 8000-8100 drop

NAT with nftables

BASH
text
# Crear tabla NAT
nft add table ip nat

# Cadenas de pre y postrouting
nft add chain ip nat prerouting \
  '{ type nat hook prerouting priority -100 ; }'
nft add chain ip nat postrouting \
  '{ type nat hook postrouting priority 100 ; }'

# MASQUERADE
nft add rule ip nat postrouting oif eth0 masquerade

# SNAT con IP estática
nft add rule ip nat postrouting ip saddr 192.168.1.0/24 oif eth0 \
  snat to 203.0.113.10

# DNAT -- reenvío de puerto 80 a servidor interno
nft add rule ip nat prerouting iif eth0 tcp dport 80 \
  dnat to 192.168.1.100:8080

Rate limiting in nftables

BASH
text
# Limitar nuevas conexiones SSH a 3 por minuto por IP de origen
nft add rule inet filter input tcp dport 22 ct state new \
  meter ssh_limit { ip saddr limit rate 3/minute } accept

# Descartar lo que supere el límite
nft add rule inet filter input tcp dport 22 ct state new drop

# Limitar ICMP para prevenir ping flood
nft add rule inet filter input icmp type echo-request \
  limit rate 10/second burst 20 packets accept

Logging in nftables

BASH
text
# Registrar y luego descartar (dos reglas consecutivas)
nft add rule inet filter input limit rate 5/minute \
  log prefix "nft-DROP: " level warn
nft add rule inet filter input drop

# Logging solo de TCP sin establecer en puertos críticos
nft add rule inet filter input tcp dport 23 \
  log prefix "TELNET-BLOCKED: " level warn drop

Sets and maps: the superpower of nftables

The sets They are lists of elements (IPs, ports, ranges) that can be referenced in multiple rules. They are much more efficient than adding a rule for each element:

BASH
text
# Crear un set de IPs bloqueadas
nft add set inet filter blocklist \
  '{ type ipv4_addr ; flags interval ; }'

# Añadir elementos al set
nft add element inet filter blocklist \
  { 203.0.113.0/24, 198.51.100.42, 192.0.2.0/28 }

# Usar el set en una regla
nft add rule inet filter input ip saddr @blocklist drop

# Añadir o eliminar elementos en caliente (sin recargar el firewall)
nft add element inet filter blocklist { 10.0.0.99 }
nft delete element inet filter blocklist { 10.0.0.99 }

# Set con timeout automático (ideal para bloqueos temporales)
nft add set inet filter temp_block \
  '{ type ipv4_addr ; flags dynamic,timeout ; timeout 1h ; }'

The maps (maps) allow key-value associations to be made, useful for dynamic DNAT or assignment of marks by IP:

BASH
text
# Mapa de DNAT: puerto de destino -> servidor interno
nft add map ip nat portmap \
  '{ type inet_service : ipv4_addr . inet_service ; }'

nft add element ip nat portmap \
  { 80 : 192.168.1.10 . 8080, 443 : 192.168.1.10 . 8443 }

nft add rule ip nat prerouting iif eth0 \
  dnat to tcp dport map @portmap

Delete rules and manage the ruleset

BASH
text
# Ver handles (identificadores) de las reglas
nft --handle list chain inet filter input

# Eliminar una regla por su handle
nft delete rule inet filter input handle 7

# Vaciar una cadena
nft flush chain inet filter input

# Eliminar una tabla completa
nft delete table inet filter

# Limpiar todo el ruleset
nft flush ruleset

Persistence in nftables

BASH
Bash
# Volcar el ruleset completo a un archivo
nft list ruleset > /etc/nftables.conf

# Cargar el ruleset desde archivo (operación atómica)
nft --file /etc/nftables.conf

# Validar sintaxis sin aplicar los cambios
nft --check --file /etc/nftables.conf

# Habilitar el servicio de nftables en systemd
systemctl enable nftables
systemctl start nftables

Migration from iptables to nftables

iptables-translate

The tool iptables-translate converts iptables rules to the nftables equivalent. It is the fastest way to migrate existing rules:

BASH
Bash
# Traducir una regla individual
iptables-translate --append INPUT --protocol tcp --dport 22 --jump ACCEPT
# Salida: nft add rule ip filter INPUT tcp dport 22 counter accept

# Traducir todas las reglas guardadas
iptables-save | iptables-restore-translate > /etc/nftables.conf

# Revisar el archivo generado antes de aplicarlo
nft --check --file /etc/nftables.conf

# Aplicar si todo es correcto
nft --file /etc/nftables.conf

iptables-nft compatibility layer

In RHEL 8+ and modern distributions, the commands iptables, ip6tables, arptables and ebtables They are actually wrappers that write rules to the nftables backend. You can verify it:

BASH
sql
# Comprobar qué iptables está en uso
update-alternatives --list iptables

# Cambiar entre el legado y la versión nft
update-alternatives --set iptables /usr/sbin/iptables-nft
update-alternatives --set iptables /usr/sbin/iptables-legacy

# Las reglas escritas con iptables-nft son visibles desde nft
iptables-nft --append INPUT --protocol tcp --dport 80 --jump ACCEPT
nft list ruleset  # aparecerán en la tabla ip iptables

Practical migration example

BASH
Bash
# Paso 1: Exportar reglas iptables actuales
iptables-save > /tmp/iptables-backup.rules

# Paso 2: Traducir a sintaxis nftables
iptables-restore-translate --file /tmp/iptables-backup.rules \
  > /tmp/nftables-migrated.conf

# Paso 3: Revisar y limpiar el archivo generado
# (eliminar reglas redundantes, mejorar con sets, etc.)

# Paso 4: Validar sin aplicar
nft --check --file /tmp/nftables-migrated.conf

# Paso 5: Hacer backup del ruleset nftables actual
nft list ruleset > /etc/nftables.conf.bak

# Paso 6: Aplicar las nuevas reglas
nft flush ruleset
nft --file /tmp/nftables-migrated.conf

# Paso 7: Verificar conectividad y guardar permanentemente
nft list ruleset > /etc/nftables.conf

Complete Example: Web Server Firewall

The next file /etc/nftables.conf implements a complete firewall for a web server with SSH, HTTP/HTTPS, rate limiting and logging. It is charged atomicly with nft -f:

BASH
Bash
#!/usr/sbin/nft -f
# /etc/nftables.conf -- Firewall para servidor web
# Cargar: nft -f /etc/nftables.conf
# Validar: nft --check -f /etc/nftables.conf

flush ruleset

# -------------------------------------------------------
# Tabla principal (inet = IPv4 + IPv6)
# -------------------------------------------------------
table inet filter {

  # Set de IPs bloqueadas -- añadir elementos en caliente
  set blocklist {
    type ipv4_addr
    flags interval
    # elementos: { 203.0.113.0/24, 198.51.100.42 }
  }

  # Set de IPs de administración (solo estas pueden entrar por SSH)
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 192.0.2.10, 10.0.0.0/8 }
  }

  chain input {
    type filter hook input priority 0
    policy drop

    # Loopback -- siempre permitir
    iif lo accept comment "Permitir loopback"

    # Conexiones ya establecidas
    ct state established,related accept comment "Permitir conexiones establecidas"

    # Paquetes inválidos -- descartar antes de procesar
    ct state invalid drop comment "Descartar paquetes inválidos"

    # Bloquear IPs de la lista negra
    ip saddr @blocklist drop comment "Bloquear IPs en blocklist"

    # ICMP (ping) con límite de tasa
    icmp type echo-request limit rate 10/second burst 20 packets accept
    icmpv6 type echo-request limit rate 10/second burst 20 packets accept

    # SSH -- solo desde IPs de administración, con rate limiting
    ip saddr @admin_ips tcp dport 22 ct state new \
      meter ssh_rate { ip saddr limit rate 5/minute } \
      accept comment "SSH para administradores"

    ip saddr @admin_ips tcp dport 22 ct state new \
      log prefix "SSH-RATE-LIMIT: " level warn drop

    # HTTP y HTTPS -- acceso público
    tcp dport { 80, 443 } ct state new \
      meter http_rate { ip saddr limit rate 100/second burst 200 } \
      accept comment "HTTP/HTTPS público"

    tcp dport { 80, 443 } ct state new \
      log prefix "HTTP-RATE-LIMIT: " level warn drop

    # Registrar y descartar todo lo demás
    limit rate 5/minute log prefix "nft-INPUT-DROP: " level warn
    drop
  }

  chain forward {
    type filter hook forward priority 0
    policy drop
    comment "Servidor independiente: no reenviar paquetes"
  }

  chain output {
    type filter hook output priority 0
    policy accept
    comment "Tráfico de salida: permitir todo"
  }
}

To apply this firewall and enable it at boot:

BASH
Bash
# Validar la sintaxis sin aplicar
nft --check --file /etc/nftables.conf

# Aplicar (operación atómica -- si falla, no se aplica nada)
nft --file /etc/nftables.conf

# Verificar que las reglas están activas
nft list ruleset

# Habilitar para el arranque
systemctl enable --now nftables

Good practices in 2026

Use nftables for new deployments

If you're setting up a server from scratch today, use nftables directly. iptables still works, but its development is frozen. Active maintenance, new features and integration with modern tools (such as firewalld either systemd-networkd) are in nftables.

Atomic loading of rules

Never apply rules one by one with nft add rule in a production environment. Always use nft --file with a file that includes flush ruleset at the beginning. This ensures that the transition between the old and new state is instantaneous, without vulnerable time windows or inconsistent rules.

Use sets for lists of IPs and ports

A rule that references a set of 1000 IPs is much more efficient than 1000 individual rules. The kernel evaluates sets with optimized data structures (interval trees, hash tables), not with linear search. Additionally, you can modify the content of a hot set without reloading the firewall:

BASH
text
# Bloquear una IP temporalmente sin recargar el firewall
nft add element inet filter blocklist { 203.0.113.99 }

# Desbloquear
nft delete element inet filter blocklist { 203.0.113.99 }

Log before drop

Always add a logging rule just before the final drops, even with rate limiting so as not to saturate the log. Without logging, diagnosing why a service is not accessible becomes an exercise in guesswork. The log prefix (log prefix "...") is key to filtering events in journalctl:

BASH
text
# Filtrar logs del firewall
journalctl -k --grep "nft-INPUT-DROP"

# En tiempo real
journalctl -kf | grep "nft-"

Document your rules with comments

nftables supports inline comments with comment "...". Use them. An uncommented rule that blocks traffic without explanation is a maintenance issue waiting for its time:

BASH
Bash
tcp dport 8443 accept comment "API interna -- ver ticket INFRA-2847"
ip saddr 192.0.2.0/24 drop comment "Bloqueo ISP abusivo -- revisar en Q3 2026"

Always validate with --check before applying

The flag --check (either -c) parses and validates the configuration file without applying any changes to the kernel. It's free and can save you from a lockout due to a typo:

BASH
php
nft --check --file /etc/nftables.conf && echo "OK" || echo "ERROR en la configuracion"

Manage the firewall under version control

The file /etc/nftables.conf must be in a Git repository. Each change in the firewall is a commit with a descriptive message. If you work with Ansible, Puppet or Salt, manage rules as code (Infrastructure as Code). A firewall change without an audit is a security risk and an operational problem.

Do an automatic rollback when changing rules remotely

When modifying rules on a remote server, always schedule an automatic rollback in case you lose access:

BASH
php
# Programar rollback en 5 minutos si no se cancela
nft list ruleset > /tmp/nft-backup.conf
(sleep 300 && nft --file /tmp/nft-backup.conf && \
  echo "ROLLBACK ejecutado automaticamente") &
ROLLBACK_PID=$!

# Aplicar nuevas reglas
nft --file /etc/nftables-new.conf

# Si todo funciona, cancelar el rollback
kill $ROLLBACK_PID && echo "Rollback cancelado -- reglas aplicadas correctamente"

The distance between a well-configured firewall and an unreachable server is a single poorly written rule. The difference between an experienced sysadmin and one who learns the hard way is having the rollback prepared before touching anything.

:wq!

Comments