Fail2Ban lee los logs de tus servicios, detecta patrones de abuso como contraseñas fallidas o escaneos, y bloquea temporalmente las IP responsables en el cortafuegos. Con la configuración por defecto solo protege SSH, pero su verdadera utilidad está en las jails propias. En este tutorial configurarás Fail2Ban en Ubuntu 24.04 para bloquear con nftables, aplicar bloqueos progresivos a los reincidentes, proteger un formulario de login servido por Nginx con un filtro propio y enviar un aviso a un webhook cada vez que se bloquea una IP.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Nginx instalado y sirviendo un sitio con un formulario de login en /login, si quieres seguir el ejemplo de la jail propia. Si tu aplicación usa otra ruta, adapta la expresión regular.
  • La IP pública desde la que administras el servidor (your_admin_ip), para no bloquearte a ti mismo.

Paso 1: Instalar Fail2Ban

Instala el paquete desde los repositorios de Ubuntu:

sudo apt update
sudo apt install -y fail2ban

Habilita y arranca el servicio:

sudo systemctl enable --now fail2ban

Comprueba que responde y qué jails tiene activas. En Ubuntu, el paquete activa la jail sshd por defecto:

sudo fail2ban-client status
Status
|- Number of jail:	1
`- Jail list:	sshd

Paso 2: Entender la estructura de configuración

Nunca edites jail.conf ni los ficheros originales de filter.d y action.d: se sobrescriben al actualizar el paquete. Fail2Ban lee los ficheros en este orden y cada uno sobrescribe al anterior:

FicheroUso
/etc/fail2ban/jail.confValores por defecto del paquete, no se toca
/etc/fail2ban/jail.d/*.confAjustes de la distribución (defaults-debian.conf)
/etc/fail2ban/jail.localTus valores globales
/etc/fail2ban/jail.d/*.localTus jails, una por fichero
/etc/fail2ban/filter.d/*.confFiltros: qué líneas de log cuentan como fallo
/etc/fail2ban/action.d/*.confAcciones: qué se hace al bloquear o desbloquear

Paso 3: Definir los valores globales

Crea jail.local con los valores que se aplicarán a todas las jails:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# No bloquear nunca estas direcciones
ignoreip = 127.0.0.1/8 ::1 your_admin_ip

# Ventana de detección, fallos permitidos y duración del primer bloqueo
findtime = 10m
maxretry = 5
bantime = 1h

# Bloqueos progresivos: cada nuevo bloqueo de la misma IP dura más
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w

# Bloquear con nftables en lugar de iptables
banaction = nftables-multiport
banaction_allports = nftables-allports

Con bantime.increment activo, una IP que vuelve a ser bloqueada pasa de 1 hora a 2, luego a 4, y así hasta el máximo de una semana. Fail2Ban recuerda los bloqueos anteriores en su base de datos SQLite, que por defecto purga las entradas de más de un día. Para que el historial dure tanto como el bloqueo más largo, crea fail2ban.local:

sudo nano /etc/fail2ban/fail2ban.local
[Definition]
dbpurgeage = 8d

Paso 4: Endurecer la jail de SSH

El filtro sshd incluido admite varios modos. El modo aggressive combina los modos normal, ddos y extra, y detecta también desconexiones durante la negociación y fallos de clave pública, que son típicos de los escáneres. Crea un fichero para la jail:

sudo nano /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
mode = aggressive
port = ssh
maxretry = 3

Si SSH escucha en otro puerto, indícalo en port (por ejemplo port = 2222), porque es el puerto que se cierra a la IP bloqueada.

Valida toda la configuración antes de recargar:

sudo fail2ban-client -t
OK: configuration test is successful

Aplica los cambios:

sudo fail2ban-client reload
sudo fail2ban-client get sshd maxretry
3

Paso 5: Crear un filtro propio para Nginx

Un filtro es una lista de expresiones regulares (failregex) donde <HOST> marca la IP que se va a bloquear. Este ejemplo detecta intentos de login fallidos en una aplicación que responde con 401 a POST /login. Así se ve una línea del log de acceso de Nginx en formato combinado:

203.0.113.45 - - [25/Sep/2026:12:01:33 +0000] "POST /login HTTP/1.1" 401 512 "-" "Mozilla/5.0"

Crea el filtro:

sudo nano /etc/fail2ban/filter.d/nginx-login.conf
[Definition]
failregex = ^<HOST> \S+ \S+ \[[^\]]+\] "POST /login[^"]*" 401 
ignoreregex =

La marca de tiempo entre corchetes la reconoce Fail2Ban automáticamente, así que no hace falta definir datepattern. Antes de usar el filtro, pruébalo con fail2ban-regex, que acepta una línea de texto o un fichero de log:

sudo fail2ban-regex '203.0.113.45 - - [25/Sep/2026:12:01:33 +0000] "POST /login HTTP/1.1" 401 512 "-" "Mozilla/5.0"' /etc/fail2ban/filter.d/nginx-login.conf
Results
=======

Failregex: 1 total
|-  #) [# of hits] regular expression
|   1) [1] ^<HOST> \S+ \S+ \[[^\]]+\] "POST /login[^"]*" 401 
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour:Minute:Second(?:\.Microseconds)?(?: Zone offset)?
`-

Lines: 1 lines, 0 ignored, 1 matched, 0 missed

Pruébalo también contra el log real para asegurarte de que no coincide con peticiones legítimas:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-login.conf

Revisa la línea Lines: del final: el número de coincidencias debe corresponder solo a logins fallidos.

Paso 6: Crear la jail para el filtro

Una jail une un filtro, un log y una acción. Crea la jail para el login:

sudo nano /etc/fail2ban/jail.d/nginx-login.local
[nginx-login]
enabled = true
filter = nginx-login
logpath = /var/log/nginx/access.log
port = http,https
maxretry = 5
findtime = 5m
bantime = 30m

Valida y recarga:

sudo fail2ban-client -t
sudo fail2ban-client reload
sudo fail2ban-client status nginx-login
Status for the jail: nginx-login
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- File list:	/var/log/nginx/access.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

Para probarla sin bloquearte, lanza desde otra máquina (no desde your_admin_ip, que está en ignoreip) seis peticiones fallidas contra tu sitio y vuelve a consultar el estado. La IP de esa máquina aparecerá en Banned IP list.

Paso 7: Bloquear a los reincidentes con recidive

La jail recidive lee el propio log de Fail2Ban y bloquea en todos los puertos a las IP que han sido bloqueadas varias veces por cualquier jail. Es complementaria a bantime.increment: esta afecta a una jail concreta, mientras que recidive cierra el servidor entero. Crea el fichero:

sudo nano /etc/fail2ban/jail.d/recidive.local
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
findtime = 1d
maxretry = 5
bantime = 1w

Recarga y comprueba que ya son tres jails:

sudo fail2ban-client reload
sudo fail2ban-client status
Status
|- Number of jail:	3
`- Jail list:	nginx-login, recidive, sshd

Paso 8: Recibir avisos con una acción propia

Las acciones son comandos que Fail2Ban ejecuta al arrancar, parar, bloquear o desbloquear. Esta acción envía un mensaje a un webhook compatible con Discord (el campo content) cada vez que se bloquea una IP. Para Slack, cambia content por text. Instala curl si no lo tienes y crea la acción:

sudo nano /etc/fail2ban/action.d/webhook-notify.conf
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -fsS -m 10 -H "Content-Type: application/json" -d '{"content": "Fail2Ban [<name>] ha bloqueado <ip> tras <failures> fallos en <fq-hostname>"}' "<webhook>"
actionunban =

[Init]
webhook =

Las etiquetas entre <> las sustituye Fail2Ban: <name> es la jail, <ip> la dirección bloqueada, <failures> el número de fallos y <fq-hostname> el nombre del servidor. Para usarla en la jail de SSH sin perder el bloqueo en el cortafuegos, combínala con la acción por defecto %(action_)s. Edita la jail:

sudo nano /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
mode = aggressive
port = ssh
maxretry = 3
action = %(action_)s
         webhook-notify[webhook="https://discord.com/api/webhooks/your_webhook_id/your_webhook_token"]

Sustituye la URL por la de tu webhook. Recarga y fuerza un bloqueo manual con una IP de documentación para probar la acción:

sudo fail2ban-client reload
sudo fail2ban-client set sshd banip 203.0.113.99
1

Deberías recibir el mensaje en tu canal. Si no llega, revisa el log de Fail2Ban con sudo grep -i webhook /var/log/fail2ban.log. Después, desbloquea la IP de prueba:

sudo fail2ban-client set sshd unbanip 203.0.113.99

Paso 9: Administrar los bloqueos

Estos son los comandos que usarás a diario. Ver todas las IP bloqueadas, agrupadas por jail:

sudo fail2ban-client banned
[{'sshd': ['198.51.100.23', '192.0.2.77']}, {'nginx-login': []}, {'recidive': []}]

Desbloquear una IP en todas las jails, útil si has bloqueado a un cliente por error:

sudo fail2ban-client unban 198.51.100.23

Ver las reglas que Fail2Ban ha creado en nftables. Las acciones nftables-* usan una tabla propia, así que no interfieren con UFW:

sudo nft list table inet f2b-table

Seguir la actividad en tiempo real:

sudo tail -f /var/log/fail2ban.log
2026-09-25 12:14:02,118 fail2ban.filter  [4021]: INFO    [sshd] Found 198.51.100.23 - 2026-09-25 12:14:01
2026-09-25 12:14:05,407 fail2ban.actions [4021]: NOTICE  [sshd] Ban 198.51.100.23

Los bloqueos sobreviven a los reinicios de Fail2Ban y del servidor: al arrancar, Fail2Ban restaura desde su base de datos las IP cuyo bloqueo aún no ha caducado.

Solución de problemas

  • Una jail no arranca y fail2ban-client -t da error de logpath: el fichero de log no existe. Crea el servicio o el log antes de activar la jail, o corrige la ruta.
  • El filtro no detecta nada aunque hay fallos en el log: prueba con fail2ban-regex usando el log real y la opción --print-all-missed para ver qué líneas no coinciden y por qué.
  • Las IP bloqueadas siguen llegando a Nginx: si el servidor está detrás de un proxy o CDN, todas las peticiones llegan desde la IP del proxy y el bloqueo en el cortafuegos no sirve. Configura Nginx para registrar la IP real del cliente con el módulo realip y bloquea en el proxy.
  • Te has bloqueado a ti mismo: entra por la consola VNC del panel de tu proveedor y ejecuta sudo fail2ban-client unban tu_ip. Después añade tu IP a ignoreip.

Conclusión

Tu servidor bloquea ahora con nftables, alarga los bloqueos de los reincidentes, protege un login web con un filtro propio probado con fail2ban-regex y te avisa en cada bloqueo. A partir de aquí puedes crear jails para otros servicios que registren fallos (Postfix, Dovecot, paneles de administración), combinar Fail2Ban con reglas de limitación de conexiones en nftables o centralizar sus logs en un SIEM como Wazuh.