iptables: Guía Completa 2026 — Comandos, Reglas y Seguridad en Linux

iptables es la herramienta de espacio de usuario que permite configurar el framework netfilter del kernel Linux. Actúa como firewall, controlando el tráfico entrante, saliente y en tránsito de cualquier servidor Linux. Lleva más de dos décadas siendo el estándar de facto — y sigue siendo una habilidad imprescindible para cualquier administrador de sistemas.

Resumen rápido: iptables organiza sus reglas en 5 tablas (filter, nat, mangle, raw, security) y 5 cadenas (INPUT, OUTPUT, FORWARD, PREROUTING, POSTROUTING). Esta guía cubre desde la instalación hasta scripts de producción con anti-DDoS, protección SSH y NAT completo.


¿Qué es iptables?

iptables es la interfaz de línea de comandos para el módulo netfilter del kernel Linux. Proporciona control total sobre el tráfico de red: filtra paquetes, gestiona NAT, reenvía puertos, registra eventos y protege el servidor contra ataques como fuerza bruta o DDoS.

Introducido en el kernel 2.4 (año 2000), su longevidad se debe a su potencia, flexibilidad y la enorme cantidad de documentación y herramientas construidas a su alrededor.

ℹ️ Nota 2025: En distribuciones modernas como Ubuntu 22.04+ y Debian 12, el comando iptables es en realidad un frontend de nftables mediante la capa de compatibilidad iptables-nft. Los comandos son idénticos — esta guía aplica igualmente.

Casos de uso principales:

  • Filtrado de paquetes: bloquear o permitir tráfico por IP, puerto o protocolo
  • Firewall con estado (stateful): tracking de conexiones TCP/UDP
  • NAT (Network Address Translation): compartir IPs, reenvío de puertos
  • Protección contra ataques: DDoS, fuerza bruta, escaneo de puertos
  • QoS y marcado de tráfico para gestión de ancho de banda
  • Logging y auditoría de tráfico de red

Arquitectura: tablas y cadenas

Tablas

iptables organiza las reglas en 5 tablas según su propósito. La tabla predeterminada (si no especificas -t) es filter.

TablaPropósitoCadenas disponiblesCuándo usarla
filterFiltrado de paquetesINPUT, OUTPUT, FORWARDReglas de firewall estándar: permitir/bloquear tráfico
natTraducción de direccionesPREROUTING, POSTROUTING, OUTPUTNAT, MASQUERADE, reenvío de puertos
mangleAlteración de paquetesTodas las cadenasModificar TTL, TOS, marcar paquetes para QoS
rawExcepciones conntrackPREROUTING, OUTPUTSaltarse el seguimiento de conexiones (NOTRACK) para rendimiento
securityControl de acceso MACINPUT, OUTPUT, FORWARDIntegración con SELinux (uso infrecuente en la práctica)

Cadenas

Las cadenas son listas de reglas que se aplican en puntos específicos del recorrido de un paquete. Las reglas se evalúan de arriba a abajo; la primera que coincide determina qué le ocurre al paquete.

CadenaAplica aDescripción
INPUTPaquetes entrantesPaquetes destinados al propio sistema local
OUTPUTPaquetes salientesPaquetes generados por el sistema local
FORWARDPaquetes en tránsitoPaquetes que pasan a través del sistema (router/gateway)
PREROUTINGAntes del routingAntes de decidir si el paquete es local o a reenviar
POSTROUTINGDespués del routingJusto antes de que el paquete salga del sistema

Objetivos (targets)

ObjetivoEfecto
ACCEPTPermite el paquete
DROPDescarta el paquete silenciosamente (sin respuesta al emisor)
REJECTDescarta el paquete y envía error ICMP al emisor
LOGRegistra el paquete en syslog y continúa evaluando reglas
RETURNRegresa a la cadena que llamó a la cadena actual
MASQUERADENAT dinámico (solo en tabla nat)
DNATCambia IP/puerto de destino (solo en tabla nat)
SNATCambia IP/puerto de origen (solo en tabla nat)

Flujo de paquetes

Entender el orden exacto en que un paquete pasa por las tablas y cadenas es crítico para escribir reglas correctas:

Paquete entrante
       ↓
raw:PREROUTING → mangle:PREROUTING → nat:PREROUTING (DNAT)
       ↓
  ¿Destinado a este host?
   ↙                          ↘
SÍ (local)                 NO (reenviar)
   ↓                          ↓
mangle:INPUT             mangle:FORWARD
filter:INPUT             filter:FORWARD
   ↓                          ↓
Proceso local           mangle:POSTROUTING
   ↓                    nat:POSTROUTING → Salida
filter:OUTPUT
nat:OUTPUT
   ↓
mangle:POSTROUTING → nat:POSTROUTING → Salida

⚠️ Importante: NAT (DNAT/MASQUERADE) solo se aplica al primer paquete de una conexión. Los paquetes posteriores de la misma conexión son gestionados automáticamente por conntrack, sin volver a pasar por las reglas NAT.


Instalación y verificación

bash

# Ubuntu/Debian
sudo apt update && sudo apt install iptables iptables-persistent -y

# Verificar versión
iptables --version

# Ver reglas activas (con contadores, sin DNS, con número de línea)
sudo iptables -L -n -v --line-numbers

# Ver tabla NAT
sudo iptables -t nat -L -n -v

bash

# CentOS/RHEL/Rocky Linux
sudo dnf install iptables-services -y
sudo systemctl enable --now iptables

# Deshabilitar firewalld si está activo
sudo systemctl stop firewalld
sudo systemctl disable firewalld

Comandos básicos

Sintaxis general

iptables [-t tabla] ACCIÓN CADENA [criterios] -j OBJETIVO
AcciónSignificado
-AAppend — añadir regla al final de la cadena
-IInsert — insertar en posición concreta (por defecto: 1)
-DDelete — eliminar regla
-RReplace — reemplazar regla en posición
-LList — listar reglas
-FFlush — vaciar todas las reglas de una cadena
-NNew — crear cadena personalizada
-XDelete chain — eliminar cadena personalizada vacía
-PPolicy — establecer política por defecto
-ZZero — poner a cero los contadores

Listar reglas

bash

# Listado completo con contadores, sin DNS, con números de línea
sudo iptables -L -n -v --line-numbers

# Solo cadena INPUT
sudo iptables -L INPUT -n -v --line-numbers

# Ver en formato de comandos ejecutables (ideal para scripts)
sudo iptables -S

# Ver tabla NAT
sudo iptables -t nat -L -n -v --line-numbers

# Ver todas las tablas
for TABLE in filter nat mangle raw security; do
  echo "=== Tabla: $TABLE ==="
  sudo iptables -t $TABLE -L -n --line-numbers
done

Gestionar reglas

bash

# Añadir al final (-A)
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Insertar en primera posición (-I) — máxima prioridad
sudo iptables -I INPUT 1 -s 10.0.0.5 -j ACCEPT

# Insertar en posición 5
sudo iptables -I INPUT 5 -p tcp --dport 80 -j ACCEPT

# Eliminar por especificación exacta
sudo iptables -D INPUT -p tcp --dport 80 -j ACCEPT

# Eliminar por número de línea (más seguro)
sudo iptables -D INPUT 3

# Verificar si una regla existe sin aplicarla (-C = check)
sudo iptables -C INPUT -p tcp --dport 80 -j ACCEPT
echo $?  # 0 = existe, 1 = no existe

# Vaciar cadena específica
sudo iptables -F INPUT

# Reseteo completo (¡cuidado en producción!)
sudo iptables -F && sudo iptables -X
sudo iptables -t nat -F && sudo iptables -t mangle -F
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD ACCEPT

Firewall básico

🚨 Advertencia crítica: Antes de establecer política DROP en INPUT, siempre añade primero la regla de SSH y las conexiones establecidas. De lo contrario te quedarás sin acceso al servidor. Ten siempre preparado un acceso por consola/KVM como alternativa.

Orden correcto para no quedarte bloqueado:

  1. Permitir interfaz loopback (lo)
  2. Permitir conexiones ya establecidas (ESTABLISHED, RELATED)
  3. Descartar paquetes inválidos
  4. Permitir SSH explícitamente
  5. Añadir el resto de servicios
  6. Establecer política DROP como último paso

bash

#!/bin/bash
# firewall-basico.sh — Servidor web con SSH

# 1. Loopback — siempre primero
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT

# 2. Conexiones ya establecidas — evita cortar sesiones activas
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 3. Descartar paquetes inválidos (antes de otras reglas)
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 4. SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 5. HTTP y HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 6. ICMP (ping) con límite de velocidad
sudo iptables -A INPUT -p icmp --icmp-type echo-request \
  -m limit --limit 1/s --limit-burst 5 -j ACCEPT

# 7. Política por defecto — ÚLTIMO PASO
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

echo "✓ Firewall básico configurado"

Coincidencias avanzadas

Puertos y protocolos

bash

# Puerto único
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT   # MySQL
iptables -A INPUT -p udp --dport 53 -j ACCEPT      # DNS

# Rango de puertos
iptables -A INPUT -p tcp --dport 8000:8999 -j ACCEPT

# Múltiples puertos (módulo multiport — hasta 15 puertos)
iptables -A INPUT -p tcp -m multiport \
  --dports 80,443,8080,8443 -j ACCEPT

# Puerto de origen específico
iptables -A INPUT -p tcp --sport 1024:65535 -j ACCEPT

IPs y subredes

bash

# IP de origen específica
iptables -A INPUT -s 192.168.1.100 -j ACCEPT

# Subred completa
iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT

# Negación (todo excepto esta IP)
iptables -A INPUT -s ! 192.168.1.100 -p tcp --dport 3306 -j DROP

# Rango de IPs (módulo iprange)
iptables -A INPUT -m iprange \
  --src-range 192.168.1.10-192.168.1.20 -j ACCEPT

Seguimiento de conexiones (stateful firewall)

El módulo conntrack permite crear reglas basadas en el estado de la conexión TCP/UDP:

EstadoDescripciónAcción típica
NEWPrimer paquete de una nueva conexión (SYN)Evaluar reglas de acceso
ESTABLISHEDPertenece a una conexión ya rastreadaACCEPT siempre
RELATEDNueva conexión relacionada con una existente (ej: FTP data)ACCEPT generalmente
INVALIDNo encaja en ninguna conexión conocida; posible ataqueDROP siempre
UNTRACKEDExcluido del tracking con NOTRACK en tabla rawSegún política

bash

# Forma moderna con conntrack (recomendada)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Forma clásica con state (equivalente, más antigua)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Ver conexiones actuales rastreadas
sudo conntrack -L

# Ver estadísticas del tracking
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

Rate limiting (limitación de tasa)

bash

# Módulo limit: --limit (velocidad media) y --limit-burst (ráfaga inicial)

# Limitar pings a 1 por segundo (ráfaga inicial de 5)
iptables -A INPUT -p icmp --icmp-type echo-request \
  -m limit --limit 1/s --limit-burst 5 -j ACCEPT

# Limitar nuevas conexiones HTTP (25/min, ráfaga de 100)
iptables -A INPUT -p tcp --dport 80 \
  -m conntrack --ctstate NEW \
  -m limit --limit 25/min --limit-burst 100 -j ACCEPT

# Módulo connlimit: limita conexiones simultáneas por IP
# Bloquear si una IP tiene más de 50 conexiones HTTP simultáneas
iptables -A INPUT -p tcp --dport 80 \
  -m connlimit --connlimit-above 50 -j REJECT

# Limitar conexiones SSH simultáneas a 3 por IP
iptables -A INPUT -p tcp --dport 22 \
  -m connlimit --connlimit-above 3 -j REJECT --reject-with tcp-reset

Coincidencia por interfaz y MAC

bash

# Por interfaz de entrada/salida
iptables -A INPUT -i eth0 -j ACCEPT
iptables -A OUTPUT -o eth1 -j ACCEPT

# Por dirección MAC
iptables -A INPUT -m mac --mac-source 00:11:22:33:44:55 -j ACCEPT
iptables -A INPUT -m mac --mac-source AA:BB:CC:DD:EE:FF -j DROP

# Reglas basadas en tiempo (módulo time)
iptables -A INPUT -p tcp --dport 22 \
  -m time --weekdays Mon,Tue,Wed,Thu,Fri \
  --timestart 09:00 --timestop 18:00 -j ACCEPT

NAT: Network Address Translation

MASQUERADE — compartir conexión a internet

MASQUERADE es SNAT dinámico: sustituye la IP de origen por la de la interfaz de salida. Ideal cuando tu IP pública puede cambiar.

bash

# 1. Habilitar IP forwarding (obligatorio para NAT)
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-forwarding.conf
sudo sysctl -p /etc/sysctl.d/99-forwarding.conf

# 2. MASQUERADE en interfaz pública (eth0)
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# 3. Permitir reenvío desde red interna (eth1) hacia internet (eth0)
sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o eth1 \
  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Alternativa: SNAT con IP fija (más eficiente que MASQUERADE)
sudo iptables -t nat -A POSTROUTING -o eth0 \
  -j SNAT --to-source 203.0.113.10

DNAT — reenvío de puertos

bash

# Puerto 80 externo → servidor web interno (192.168.1.10:80)
sudo iptables -t nat -A PREROUTING \
  -i eth0 -p tcp --dport 80 \
  -j DNAT --to-destination 192.168.1.10:80

# Puerto 2222 externo → SSH servidor interno (puerto 22)
sudo iptables -t nat -A PREROUTING \
  -i eth0 -p tcp --dport 2222 \
  -j DNAT --to-destination 192.168.1.5:22

# Puerto 443 externo → balanceador interno
sudo iptables -t nat -A PREROUTING \
  -i eth0 -p tcp --dport 443 \
  -j DNAT --to-destination 192.168.1.100:443

# Permitir el tráfico reenviado en FORWARD
sudo iptables -A FORWARD \
  -d 192.168.1.0/24 -m conntrack --ctstate NEW -j ACCEPT

# Redirección local: puerto 80 → aplicación en 8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 \
  -j REDIRECT --to-port 8080
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 \
  -j REDIRECT --to-port 8080

Registro de eventos (logging)

bash

# Patrón recomendado: cadena personalizada de logging
sudo iptables -N LOG_AND_DROP
sudo iptables -A LOG_AND_DROP \
  -m limit --limit 5/min --limit-burst 10 \
  -j LOG --log-prefix "iptables-DROP: " --log-level 6
sudo iptables -A LOG_AND_DROP -j DROP

# Usar la cadena de logging
sudo iptables -A INPUT -p tcp --dport 23 -j LOG_AND_DROP   # Telnet
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j LOG_AND_DROP

# Registrar nuevas conexiones SSH (sin bloquear)
sudo iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -j LOG --log-prefix "SSH-NEW: " --log-level 6

# Niveles de log: 0=emerg, 1=alert, 2=crit, 3=err,
#                 4=warning, 5=notice, 6=info, 7=debug

# Ver logs en tiempo real
sudo tail -f /var/log/kern.log | grep iptables
sudo journalctl -f -k | grep iptables-DROP

Hardening: protección contra ataques

bash

# ── Paquetes TCP malformados ──────────────────────────────

# NULL scan (todos los flags a 0 — indica escaneo de puertos)
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP

# XMAS scan (todos los flags a 1)
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP

# SYN+FIN (combinación inválida en TCP)
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP

# SYN+RST (combinación inválida en TCP)
iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP

# ── Anti-spoofing en interfaz pública (eth0) ─────────────
iptables -A INPUT -i eth0 -s 10.0.0.0/8 -j DROP
iptables -A INPUT -i eth0 -s 172.16.0.0/12 -j DROP
iptables -A INPUT -i eth0 -s 192.168.0.0/16 -j DROP
iptables -A INPUT -i eth0 -s 127.0.0.0/8 -j DROP
iptables -A INPUT -i eth0 -s 169.254.0.0/16 -j DROP   # APIPA
iptables -A INPUT -i eth0 -s 224.0.0.0/4 -j DROP      # Multicast
iptables -A INPUT -i eth0 -s 240.0.0.0/5 -j DROP      # Reservado

# ── SYN flood ─────────────────────────────────────────────
# Habilitar SYN cookies a nivel kernel (la defensa más efectiva)
sysctl -w net.ipv4.tcp_syncookies=1

# Complementar con iptables
iptables -A INPUT -p tcp --syn \
  -m limit --limit 10/s --limit-burst 20 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

# ── Paquetes inválidos ────────────────────────────────────
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

Protección anti-DDoS

bash

# ── HTTP flood ───────────────────────────────────────────
# Limitar nuevas conexiones: máx 20 por IP en 10 segundos
iptables -A INPUT -p tcp --dport 80 \
  -m conntrack --ctstate NEW \
  -m recent --set --name HTTP_FLOOD

iptables -A INPUT -p tcp --dport 80 \
  -m conntrack --ctstate NEW \
  -m recent --update --seconds 10 --hitcount 20 --name HTTP_FLOOD \
  -j DROP

# Limitar conexiones simultáneas por IP (máx 50)
iptables -A INPUT -p tcp --dport 80 \
  -m connlimit --connlimit-above 50 -j REJECT --reject-with tcp-reset

# ── UDP flood ────────────────────────────────────────────
iptables -A INPUT -p udp \
  -m limit --limit 100/s --limit-burst 200 -j ACCEPT
iptables -A INPUT -p udp -j DROP

# ── ICMP flood ───────────────────────────────────────────
iptables -A INPUT -p icmp --icmp-type echo-request \
  -m limit --limit 2/s --limit-burst 10 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

# ── Blacklist dinámica con ipset ─────────────────────────
# Mucho más eficiente que reglas iptables individuales
# Requiere: apt install ipset

# Crear conjunto con timeout de 24h por IP
ipset create BLACKLIST hash:ip maxelem 65536 timeout 86400
iptables -A INPUT -m set --match-set BLACKLIST src -j DROP

# Añadir IP a blacklist (expira en 24h automáticamente)
ipset add BLACKLIST 1.2.3.4

# Script para poblar blacklist desde un archivo
while read IP; do ipset add BLACKLIST $IP 2>/dev/null; done < ips-maliciosas.txt

Protección contra fuerza bruta SSH

bash

# Estrategia: banear IPs que intenten más de 4 conexiones en 60 segundos

# Paso 1: Registrar cada nuevo intento de conexión SSH
iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -m recent --set --name SSH_BF --rsource

# Paso 2: Si la misma IP ha intentado ≥4 veces en 60s → LOG y DROP
iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 \
  --name SSH_BF --rsource \
  -j LOG --log-prefix "SSH-BRUTEFORCE: " --log-level 6

iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 \
  --name SSH_BF --rsource -j DROP

# Paso 3: Permitir las conexiones que pasan el filtro
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Ver IPs actualmente registradas
cat /proc/net/xt_recent/SSH_BF

# Limpiar lista manualmente
echo / | sudo tee /proc/net/xt_recent/SSH_BF

💡 Complementa con fail2ban para bloqueo dinámico basado en análisis de logs, que ofrece mayor flexibilidad y soporte para más servicios (HTTP, FTP, SMTP, etc.).


Script completo de producción

Script listo para usar en un servidor web Linux en producción, incorporando todas las buenas prácticas de esta guía:

bash

#!/bin/bash
# production-firewall.sh — Firewall de producción para Blumhost
# Uso: sudo bash production-firewall.sh
# ─────────────────────────────────────────────────────────

set -e

EXT_IF="eth0"       # Interfaz pública
SSH_PORT="22"       # Cambia si usas otro puerto
ADMIN_IP=""         # IP de admin (vacío = permitir todos con protección)

echo "[*] Configurando firewall de producción..."

# ── Reset limpio ──────────────────────────────────────────
iptables -F; iptables -X
iptables -t nat -F; iptables -t nat -X
iptables -t mangle -F; iptables -t raw -F

# ── Políticas por defecto ─────────────────────────────────
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# ── Loopback ──────────────────────────────────────────────
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# ── Conexiones establecidas ───────────────────────────────
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# ── Anti-spoofing ─────────────────────────────────────────
for NET in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8 169.254.0.0/16; do
  iptables -A INPUT -i $EXT_IF -s $NET -j DROP
done

# ── TCP malformado ────────────────────────────────────────
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP

# ── SYN flood ─────────────────────────────────────────────
iptables -A INPUT -p tcp --syn \
  -m limit --limit 10/s --limit-burst 20 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

# ── SSH ───────────────────────────────────────────────────
if [ -n "$ADMIN_IP" ]; then
  # Solo desde IP de administración
  iptables -A INPUT -p tcp --dport $SSH_PORT -s $ADMIN_IP -j ACCEPT
else
  # Protección anti-fuerza-bruta para acceso general
  iptables -A INPUT -p tcp --dport $SSH_PORT \
    -m conntrack --ctstate NEW -m recent --set --name SSH_BF
  iptables -A INPUT -p tcp --dport $SSH_PORT \
    -m conntrack --ctstate NEW \
    -m recent --update --seconds 60 --hitcount 4 --name SSH_BF -j DROP
  iptables -A INPUT -p tcp --dport $SSH_PORT -j ACCEPT
fi

# ── HTTP/HTTPS ────────────────────────────────────────────
iptables -A INPUT -p tcp -m multiport --dports 80,443 \
  -m conntrack --ctstate NEW \
  -m limit --limit 60/s --limit-burst 120 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443 \
  -m connlimit --connlimit-above 50 -j REJECT

# ── ICMP ──────────────────────────────────────────────────
iptables -A INPUT -p icmp --icmp-type echo-request \
  -m limit --limit 2/s --limit-burst 10 -j ACCEPT

# ── Log paquetes bloqueados ───────────────────────────────
iptables -A INPUT \
  -m limit --limit 5/min --limit-burst 10 \
  -j LOG --log-prefix "iptables-DROP: " --log-level 6

# ── Guardar reglas ────────────────────────────────────────
if command -v netfilter-persistent &>/dev/null; then
  netfilter-persistent save
else
  mkdir -p /etc/iptables
  iptables-save > /etc/iptables/rules.v4
fi

echo "[✓] Firewall configurado y guardado"
echo "[i] Verifica con: sudo iptables -L -n -v --line-numbers"

Guardar y restaurar reglas

Las reglas de iptables son volátiles por defecto: viven en memoria y se pierden al reiniciar. Debes guardarlas explícitamente.

Ubuntu/Debian

bash

# Instalar iptables-persistent
sudo apt install iptables-persistent -y

# Guardar reglas actuales
sudo netfilter-persistent save
# Se guardan en: /etc/iptables/rules.v4 y rules.v6

# Recargar manualmente
sudo netfilter-persistent reload

# Guardar manualmente
sudo iptables-save > /etc/iptables/rules.v4
sudo ip6tables-save > /etc/iptables/rules.v6

# Restaurar manualmente
sudo iptables-restore < /etc/iptables/rules.v4

CentOS/RHEL/Rocky Linux

bash

# Guardar reglas
sudo service iptables save
# O directamente:
sudo iptables-save > /etc/sysconfig/iptables

# Restaurar
sudo systemctl restart iptables

# Verificar que carga automáticamente al boot
sudo systemctl is-enabled iptables

Backup automatizado (cron)

bash

#!/bin/bash
# backup-iptables.sh
BACKUP_DIR="/root/iptables-backups"
mkdir -p $BACKUP_DIR
iptables-save > $BACKUP_DIR/iptables-$(date +%Y%m%d-%H%M%S).rules
# Mantener solo los últimos 30 días
find $BACKUP_DIR -name "iptables-*.rules" -mtime +30 -delete

Añadir al cron (crontab -e):

0 2 * * * /root/backup-iptables.sh

Troubleshooting

🚨 ¿Bloqueado por SSH? Accede por consola KVM/VNC y ejecuta sudo iptables -F && sudo iptables -P INPUT ACCEPT para resetear el firewall.

Diagnóstico general

bash

# Ver reglas con contadores (¿se están aplicando?)
sudo iptables -L -n -v --line-numbers

# Poner contadores a cero y reprobar
sudo iptables -Z
# ... genera tráfico de prueba ...
sudo iptables -L -n -v

# Verificar si una regla existe sin aplicarla
sudo iptables -C INPUT -p tcp --dport 80 -j ACCEPT
echo $?  # 0 = existe, 1 = no existe

# Trazar un paquete a través de todas las reglas
sudo iptables -t raw -A PREROUTING -p tcp --dport 80 -j TRACE
sudo journalctl -f -k | grep TRACE
# Quitar el trace cuando termines:
sudo iptables -t raw -D PREROUTING -p tcp --dport 80 -j TRACE

# Capturar tráfico para verificar que llega
sudo tcpdump -i eth0 -n port 80

# ¿El servicio está escuchando?
ss -tlnp | grep :80

Problemas de conntrack

bash

# Ver conexiones actualmente rastreadas
sudo conntrack -L 2>/dev/null | head -20

# Ver si el conntrack está lleno (causa común de fallos)
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

# Si count ≈ max, aumentar el límite
sudo sysctl -w net.netfilter.nf_conntrack_max=262144
echo "net.netfilter.nf_conntrack_max=262144" >> /etc/sysctl.d/99-conntrack.conf

Checklist de diagnóstico

  • ¿El servicio está escuchando? → ss -tlnp | grep :PUERTO
  • ¿El paquete llega al servidor? → tcpdump -i eth0 port PUERTO
  • ¿Las reglas se procesan en orden correcto? → iptables -L --line-numbers
  • ¿Los contadores aumentan? → iptables -L -v -n después de prueba
  • ¿El conntrack está lleno? → Comparar count vs max
  • ¿ip_forward está habilitado (si es router)? → sysctl net.ipv4.ip_forward
  • ¿Docker está interfiriendo? → Revisar cadenas DOCKER y DOCKER-USER

iptables vs nftables vs UFW

iptablesnftablesUFW
DisponibilidadUniversalLinux 3.13+Ubuntu/Debian
SintaxisVerbosa, familiarLimpia y unificadaMuy simple
IPv4 + IPv6Comandos separadosUna sola reglaAutomático
RendimientoBuenoMejorIgual a iptables
NAT avanzado✓ Completo✓ Completo✗ Limitado
Scripts legacy✓ Compatible✗ Incompatible
DocumentaciónMasivaCreciendoBásica
Curva de aprendizajeMediaMedia-altaBaja
Recomendado paraProducción, sistemas existentesProyectos nuevosEscritorio, pruebas rápidas

💡 Recomendación: Usa iptables si tienes infraestructura existente o necesitas compatibilidad máxima. Para proyectos nuevos, considera migrar directamente a nftables. Evita UFW en servidores de producción con necesidades de red complejas.


Preguntas frecuentes

¿Cuál es la diferencia entre DROP y REJECT?

DROP descarta el paquete silenciosamente: el emisor no recibe respuesta y tiene que esperar el timeout de la conexión. REJECT envía un mensaje de error ICMP («port unreachable») al emisor.

DROP es preferible en internet público: no revela información de la topología de red y dificulta el escaneo de puertos. REJECT es más adecuado en redes internas donde los clientes legítimos necesitan saber rápidamente que el acceso está denegado, sin esperar timeouts.

¿Por qué mis reglas desaparecen tras reiniciar?

Las reglas de iptables son volátiles por defecto: viven en memoria del kernel. Para hacerlas persistentes, en Ubuntu/Debian instala iptables-persistent y ejecuta sudo netfilter-persistent save. En CentOS/RHEL usa sudo service iptables save. Verifica que el servicio esté habilitado en el arranque con systemctl is-enabled iptables.

¿Afectan las reglas de iptables al tráfico IPv6?

No. iptables solo gestiona IPv4. Para IPv6 existe ip6tables con una sintaxis idéntica. Debes configurar ambos por separado. En distribuciones modernas con backend nftables (Ubuntu 22.04+), existe la opción de unificarlos en un único ruleset.

¿Es compatible iptables con Docker?

Sí, pero con precauciones. Docker modifica las reglas de iptables automáticamente (cadenas DOCKER, DOCKER-USER, DOCKER-ISOLATION-*) para gestionar la red de los contenedores. Si añades reglas en INPUT que bloquean tráfico, puede que no afecten a los contenedores porque Docker usa DNAT y FORWARD.

Para aplicar reglas a contenedores, añádelas en la cadena DOCKER-USER, que Docker respeta y no sobreescribe entre reinicios.

¿Cómo sé si una regla está funcionando?

Usa sudo iptables -L -n -v --line-numbers. La columna pkts muestra cuántos paquetes han coincidido con cada regla. Si el contador es 0 después de generar tráfico de prueba, la regla no está siendo alcanzada: revisa el orden (las reglas se procesan de arriba a abajo, la primera que coincide gana) o los criterios de coincidencia.

Usa sudo iptables -Z para poner todos los contadores a cero antes de tu prueba, y el flag -t raw -j TRACE para trazar paquetes individuales.

¿Qué pasa si bloqueo todo y pierdo el acceso SSH?

Accede al servidor por consola KVM o VNC (disponible en el panel de Blumhost) y ejecuta:

bash

sudo iptables -F
sudo iptables -P INPUT ACCEPT

Esto resetea todas las reglas y restaura el acceso. Para evitar que ocurra, siempre prueba los cambios con un script que revierta las reglas automáticamente tras un tiempo:

bash

# Aplicar nuevas reglas
iptables-restore < /root/nuevas-reglas.txt
# Revertir en 60 segundos si no confirmas
sleep 60 && iptables-restore < /root/reglas-buenas.txt &

Miguel Taboada

Ingeniero en Telecomunicaciones e Informática. Creé BlumHost para ofrecer un hosting distinto a los demás, que ofrezca la mejor atención al cliente, al menor precio y con la mejor calidad.

Ver todas las entradas

Deja una respuesta

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