Seguridad

Protege tu SSH con Fail2ban: Guía Definitiva para Sysadmins Linux 2026

Si tienes un servidor SSH expuesto a internet, los logs te lo dicen: en menos de 24 horas hay miles de intentos de login con pares comunes admin/root. Fail2ban es la primera línea de defensa y la diferencia entre un log lleno de ruido y un log con señal útil. Esta guía documenta la configuración que recomiendo: jails por servicio, integración con telegram, whitelists operativas, y el setup para que sobreviva un reboot sin reabrir todas las puertas. También cubre el caso de “fail2ban me baneó a mí mismo” — solución con knockd o con whitelist por IP fija.

Si administras un servidor con SSH en internet, esto es mínimo indispensable.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30-60 minutos, 5 minutos para mantenimiento mensual.
  • Requisitos: Debian/Ubuntu con acceso root, SSH expuesto a internet (al menos 1 puerto).
  • Resultado: 99% de brute force bloqueado, alertas opcionales a Telegram/Slack, log SSH legible.
  • Troubleshooting típico: jail activo pero no banea, regex que matchea demasiado, baneo propio. Solución: `fail2ban-client status sshd`.

Por qué tu servidor SSH ya está siendo atacado

Si tu servidor Linux tiene el puerto 22 abierto hacia internet, en este momento hay bots probando combinaciones de usuario y contraseña. No es teoría: en una auditoría típica de 24 horas sobre un servidor recién desplegado en un proveedor cloud, los logs muestran entre 800 y 4000 intentos fallidos de autenticación SSH, provenientes de rangos de IP rotativos que cambian de ASN cada pocas horas.

El patrón es predecible. Primero prueban con credenciales obvias (root, admin, ubuntu, ec2-user). Después siguen con listas de usuarios y contraseñas obtenidas de filtraciones públicas. Cuando una combinación pasa, instalan cryptominers, puertas traseras o reclutan el servidor en una botnet. Fail2ban es la primera línea de defensa automática contra ese ruido: detecta los intentos fallidos en los logs y bloquea la IP antes de que el atacante encuentre una credencial válida.

Esta guía cubre la configuración completa para 2026: instalación, jail para SSH, filtros personalizados, integración con journald, notificaciones y patrones de endurecimiento que se combinan bien con fail2ban (2FA, claves ed25519, port knocking). El objetivo es que al final tengas un servidor SSH que rechaza automáticamente el 99% del ruido automatizado sin necesidad de intervención manual.

Qué es Fail2ban exactamente

Fail2ban es un demonio escrito en Python que lee archivos de log (o el journal de systemd), aplica filtros con expresiones regulares y ejecuta acciones cuando una IP supera un umbral de fallos. La versión estable a octubre de 2026 es 1.1.1, publicada en agosto de 2026, con soporte oficial para Python 3.10+ y socket activation nativa en systemd (el servicio arranca bajo demanda cuando aparece actividad en el socket, en lugar de esperar al boot).

La arquitectura es simple:

  • Jails: definen qué log leer y qué filtro aplicar. Cada jail tiene sus propios límites (maxretry, findtime, bantime) y acciones.
  • Filters: archivos de regex que identifican líneas de log que cuentan como fallo. Para SSH el filtro por defecto cubre los mensajes estándar de OpenSSH.
  • Actions: lo que pasa cuando una IP supera el límite. Lo típico es agregar una regla iptables o nftables, pero se puede enviar mail, un webhook, un mensaje a Telegram o a un canal de Slack.
  • Database: si activas dbfile, fail2ban persiste los bans entre reinicios. Sin esa opción, un restart del servicio borra todos los bans activos.

El daemon fail2ban-server corre como root (necesita permisos para manipular reglas de firewall) y expone un socket en /var/run/fail2ban/fail2ban.sock. El cliente fail2ban-client habla con ese socket para consultar estado, agregar bans manuales o recargar la configuración.

Instalación paso a paso

En Debian y Ubuntu el paquete está en los repos oficiales:

sudo apt update
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban

Verifica que el servicio esté active (running) y que fail2ban-client ping devuelva Server replied: pong. Si falla el ping, revisa journalctl -u fail2ban -n 50 para ver el motivo.

En RHEL, Rocky Linux, AlmaLinux y Fedora la instalación usa dnf:

sudo dnf install epel-release
sudo dnf install fail2ban fail2ban-systemd
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

El paquete fail2ban-systemd habilita la integración con journald. Sin él, fail2ban en RHEL puede tener problemas para leer /var/log/secure cuando el sistema usa solo journald sin archivos de log persistentes (configuración habitual en Fedora minimal y RHEL 9+).

En Arch y derivados:

sudo pacman -S fail2ban
sudo systemctl enable --now fail2ban.service

Si tu servidor usa nftables en lugar de iptables (la opción recomendada en Debian 12+, RHEL 9 y derivados), fail2ban detecta automáticamente el backend disponible. Puedes confirmarlo con fail2ban-client get dbfile y revisando /etc/fail2ban/jail.d/defaults-debian.conf o su equivalente en tu distribución.

Configuración: por qué jail.local y no jail.conf

El archivo /etc/fail2ban/jail.conf es la plantilla distribuida por el paquete. Se sobrescribe en cada actualización. La regla de oro es nunca editar ese archivo: cualquier cambio que hagas se pierde la próxima vez que actualices fail2ban.

La solución es crear /etc/fail2ban/jail.local. Fail2ban lee primero los archivos .local, después los .conf. Esto te da una capa persistente donde van tus personalizaciones, separada de la configuración del paquete. El mismo principio aplica a los filtros: si necesitas modificar una regex, copia el filtro a /etc/fail2ban/filter.d/sshd.local en lugar de editar el original.

Estructura recomendada de jail.local:

[DEFAULT]
# Bantime en segundos. 1h es agresivo; 24h para servidores muy expuestos.
bantime = 3600
# Ventana en la que se cuentan los intentos fallidos.
findtime = 600
# Cantidad de intentos antes del ban.
maxretry = 5
# Backend: auto, polling, systemd o pyinotify.
backend = systemd
# Whitelist: nunca banear estas IPs.
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 TU_IP_CASA/32
# Persistir bans entre reinicios.
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400
# Acción por defecto: ban via nftables (multiport para SSH).
banaction = nftables-multiport
banaction_allports = nftables-allports

Qué hace cada parámetro, en lenguaje claro:

  • bantime = 3600: una IP baneada permanece bloqueada 1 hora. Para SSH expuesto a internet puedes subir a 86400 (24 h) y reducir reincidencia.
  • findtime = 600: ventana de tiempo en la que se cuentan los intentos fallidos. 10 minutos es agresivo pero efectivo.
  • maxretry = 5: 5 intentos fallidos en la ventana disparan el ban. Para SSH es razonable dejarlo en 3.
  • backend = systemd: lee los logs directamente del journal de systemd. Es más rápido y funciona aunque el archivo de log no exista (típico en Fedora minimal).
  • ignoreip: tu IP de administración debe estar acá, sino te vas a banear a ti mismo. Incluye rangos RFC1918 y la IP de tu VPN corporativa.
  • dbfile: persistencia de bans. Sin esto, un systemctl restart fail2ban libera todas las IPs baneadas.

Jail específico para SSH

Activa un jail dedicado para SSH con parámetros más estrictos que el default. En el mismo jail.local:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
backend  = systemd
maxretry = 3
findtime = 600
bantime  = 7200

Si cambiaste el puerto SSH (recomendable como complemento, no como única defensa), reemplaza port = ssh por el número real, por ejemplo port = 2222. Fail2ban usa ese valor para crear la regla de firewall.

El filtro sshd que viene por defecto reconoce los mensajes estándar de OpenSSH. Para sshd en puertos no estándar o builds personalizados, conviene duplicar y ajustar el filtro (más adelante en esta guía).

Reinicia fail2ban después de cualquier cambio en jail.local:

sudo systemctl restart fail2ban
sudo fail2ban-client status

El comando status lista todos los jails activos con su configuración y conteo de bans. Para inspeccionar el jail SSH específicamente: sudo fail2ban-client status sshd. Ahí verás las IPs actualmente baneadas, el total acumulado y la fecha del último ban.

Jails adicionales que vale la pena activar

Fail2ban trae jails preconfigurados para varios servicios. Activa los que apliquen a tu servidor:

[recidive]
enabled  = true
filter   = recidive
logpath  = /var/log/fail2ban.log
backend  = polling
bantime   = 604800
findtime  = 86400
maxretry  = 5
[sshd-ddos]
enabled  = true
filter   = sshd-ddos
backend  = systemd
maxretry = 2
findtime = 60
bantime  = 3600

El jail recidive es la red de seguridad: banea por 7 días a cualquier IP que ya haya sido baneada 5 veces en el último día. Esto frena a los atacantes que esperan a que pase el bantime y vuelven a intentar.

El jail sshd-ddos detecta intentos masivos de conexión (muchas conexiones en muy poco tiempo). Es útil contra ataques de denegación de servicio distribuidos contra el puerto SSH.

Si tu servidor expone un servidor web, activa también los jails apache-badbots, nginx-http-auth o nginx-botsearch según corresponda.

Notificaciones: Telegram, mail o webhook

Recibir una notificación cuando fail2ban banea una IP te permite reaccionar ante patrones (M218 intentos desde un ASN particular, un usuario legítimo bloqueado por error). El método más confiable y rápido es Telegram.

Crea un bot hablando con @BotFather en Telegram. Envía /newbot, sigue las instrucciones y guarda el token (formato 123456789:ABCdef...). Después manda cualquier mensaje a tu bot nuevo y visita https://api.telegram.org/bot<TOKEN>/getUpdates para obtener tu chat_id numérico.

Crea /etc/fail2ban/action.d/telegram-notify.conf:

[Definition]
token   = PONER_TU_TOKEN_AQUI
chat_id = PONER_TU_CHAT_ID_AQUI
api_url   = https://api.telegram.org/bot%(token)s/sendMessage
actionban = curl -fsS -X POST "%(api_url)s" -d chat_id=%(chat_id)s -d text="[Fail2ban] Ban IP=<ip> jail=<name> time=<bantime>s"
actionunban = curl -fsS -X POST "%(api_url)s" -d chat_id=%(chat_id)s -d text="[Fail2ban] Unban IP=<ip> jail=<name>"

En jail.local referencia la acción en el jail SSH:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
backend  = systemd
maxretry = 3
bantime  = 7200
action   = nftables-multiport[name=sshd, port="ssh", protocol=tcp]
           telegram-notify[name=sshd]

Prueba la notificación forzando un ban manual:

sudo fail2ban-client set sshd banip 1.2.3.4

Si llega el mensaje a Telegram, la integración funciona. Si no llega, revisa el token y el chat_id. Un error común es confundir el chat_id (numérico) con el @username del chat.

Filtros personalizados para ataques específicos

El filtro sshd por defecto matchea los mensajes estándar de OpenSSH. Hay patrones de ataque que no matchea y conviene agregar:

  • Escaneo de usuarios: bots que prueban una lista de nombres de usuario sin completar el handshake.
  • Ataques de fuerza bruta con listas de contraseñas: alta frecuencia pero con usuarios rotativos.
  • Tunneling de SSH-over-WebSocket: tráfico que parece SSH pero evade filtros.

Ejemplo: filtro para detectar intentos repetidos de login con usuario no existente. Crea /etc/fail2ban/filter.d/sshd-invaliduser.conf:

[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)sInvalid user S+ from <HOST>.*$
            ^%(__prefix_line)sFailed password for invalid user S+ from <HOST>.*$
            ^%(__prefix_line)sUser S+ from <HOST> not allowed because not listed in AllowUsers.*$
ignoreregex =

Activa el filtro en un jail dedicado:

[sshd-invaliduser]
enabled  = true
port     = ssh
filter   = sshd-invaliduser
backend  = systemd
maxretry = 2
findtime = 300
bantime  = 86400

Para probar la regex antes de activarla:

sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-invaliduser.conf

El comando imprime cuántos matches encontró y muestra líneas de ejemplo. Si la regex falla, ajusta antes de activar el jail.

Monitoreo y métricas

Para integrar con Prometheus y Grafana, fail2ban expone un socket UNIX que puedes consultar. Una opción limpia es usar fail2ban-exporter (complemento de la comunidad). En Debian/Ubuntu no está empaquetado, pero puedes correrlo como servicio:

sudo apt install python3-prometheus-client
cat <<'EOF' | sudo tee /usr/local/bin/fail2ban_exporter.py
#!/usr/bin/env python3
"""Expose fail2ban stats as Prometheus metrics on :9191/metrics."""
import subprocess, json
from prometheus_client import start_http_server, REGISTRY, Gauge
class FBCollector:
    def collect(self):
        out = subprocess.check_output(['fail2ban-client', 'status', '--json']).decode()
        data = json.loads(out)
        g = Gauge('fail2ban_banned_ips', 'IPs banned per jail', ['jail'])
        for jail, info in data.items():
            g.labels(jail).set(info.get('currently_banned', 0))
EOF
sudo chmod +x /usr/local/bin/fail2ban_exporter.py

Para métricas más detalladas (total bans acumulados, attempts por minuto), parsea el log de fail2ban:

sudo journalctl -u fail2ban --since "1 hour ago" | grep -c "Ban"

Si quieres alertas, configura reglas en Prometheus contra métricas de fail2ban: por ejemplo, disparar alerta si el conteo de bans por minuto supera 50 (señal de ataque distribuido activo).

Hardening complementario: lo que va junto a Fail2ban

Fail2ban no reemplaza otras medidas. La combinación correcta:

Medida Por qué complementa Cómo implementarlo
Deshabilitar login por contraseña Fail2ban solo banea por intentos fallidos. Si las contraseñas son robustas pero la clave SSH es la única vía, los ataques de fuerza bruta se vuelven inútiles. PasswordAuthentication no en /etc/ssh/sshd_config
Usar claves ed25519 Más cortas, más rápidas y resistentes a ciertos ataques criptoanalíticos que RSA o ECDSA. ssh-keygen -t ed25519 -a 100
2FA con TOTP Aun si una clave privada se filtra, el atacante necesita el código temporal. google-authenticator-libpam + libpam-google-authenticator
Cambiar el puerto SSH Reduce el ruido automatizado (no elimina el riesgo). Útil en combinación con fail2ban, no como única defensa. Port 2222 en sshd_config
Limitar usuarios permitidos Ataques que prueban admin o ubuntu no llegan ni al filtro fail2ban. AllowUsers deploy admin en sshd_config
Port knocking El puerto SSH está cerrado hasta que el cliente envía la secuencia de puertos correcta. fail2ban queda como respaldo. knockd con configuración de secuencia
GeoIP blocking Si tus administradores están en un país específico, bloquear el resto reduce superficie. xtables-addons con GeoIP o CrowdSec con listas

La combinación más efectiva en producción es: claves ed25519 (sin login por contraseña) + AllowUsers + 2FA TOTP + fail2ban + notificaciones. Eso cierra los huecos que un atacante podría aprovechar.

Troubleshooting común

Fail2ban está activo pero no banea nada. Revisa primero el backend (¿systemd o polling?). Si usas systemd, verifica que journald esté leyendo los logs de sshd: journalctl -u ssh --since "10 minutes ago" | tail. Si usas polling, confirma que el archivo de log existe y tiene permisos para fail2ban.

Las IPs se desbanean al reiniciar fail2ban. Falta configurar dbfile en la sección [DEFAULT]. Sin persistencia, los bans viven solo en memoria.

El filtro no matchea logs personalizados. Usa fail2ban-regex con un log de muestra para validar la regex. Si sshd está parcheado o compilado con banners distintos, el filtro por defecto puede no reconocer.

nftables no bloquea la IP. Confirma que el backend seleccionado es compatible con tu versión de nftables: fail2ban-client get <jail> actions. En Debian 12 con nftables como frontend de iptables-nft, la acción nftables-multiport funciona; en sistemas con iptables legacy, usa iptables-multiport.

Bans excesivos de IPs legítimas. Agrega los rangos correspondientes a ignoreip. Si el problema es un usuario que falla contraseñas legítimamente, corrige el problema en la máquina del usuario (cliente SSH que no carga la clave correctamente, configuración incorrecta).

Cuándo NO usar Fail2ban (o cuándo sumar CrowdSec)

Fail2ban tiene limitaciones legítimas. No es la herramienta correcta si:

  • Tu servidor está expuesto a un ataque distribuido a gran escala: fail2ban banea por IP, no por reputación. Si el atacante rota entre 1000 IPs cada minuto, fail2ban no bloquea el ataque, solo los intentos individuales.
  • Necesitas compartir reputación entre servidores: fail2ban es local a cada servidor. Una IP baneada en uno sigue libre en otro.
  • Trabajas en Kubernetes o con microservicios: fail2ban está pensado para servidores con IP fija, no para pods con IPs dinámicas.

Para esos casos, considera CrowdSec, que mantiene una base de datos comunitaria de IPs maliciosas compartidas entre todos los usuarios. Es complementario a fail2ban, no un reemplazo.

Resumen

Fail2ban sigue siendo en 2026 la primera línea de defensa más práctica para un servidor SSH Linux. La receta mínima es: instalar el paquete, crear jail.local con backend = systemd, activar el jail SSH con maxretry = 3, sumar el jail recidive para reincidentes, configurar notificaciones, y combinar con claves ed25519 + 2FA + AllowUsers.

Con esa base, más del 99% del tráfico automatizado queda bloqueado antes de que llegue al binario sshd. Lo que pasa después del filtro de fail2ban (claves, 2FA) es lo que protege contra el atacante dedicado.

Preguntas frecuentes

¿Por qué Protege tu SSH con Fail2ban: Guía Definitiva para Sysadmins Linux 2026 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 Protege tu SSH con Fail2ban:?

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 Protege tu SSH con Fail2ban:?

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 Protege tu SSH con Fail2ban: 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 Protege tu SSH con Fail2ban:?

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]

Deja una respuesta

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