Introducción
Todo VPS expuesto a internet recibe miles de intentos de login por día. Esto no es paranoia, es la realidad del internet: bots automatizados escanean rangos de IPs continuamente buscando servicios SSH abiertos con contraseñas débiles. En cuestión de minutos después de crear un servidor, ya tienes intentos de acceso en tus logs.
Puedes verlo tú mismo ejecutando este comando en cualquier VPS sin protección:
sudo grep "Failed password" /var/log/auth.log | wc -l
En un VPS típico sin protección, ese número puede estar entre 500 y 5000 intentos fallidos por día. No es raro ver 10.000 en servidores más expuestos. Cada uno de esos intentos es un bot probando combinaciones de usuario y contraseña, buscando esa combinación que le abra la puerta.
Fail2ban es la solución estándar para este problema: lee tus logs y banea automáticamente las IPs que muestran comportamiento sospechoso. En esta guía vas a configurar Fail2ban para proteger SSH y nginx, activar baneos prolongados para reincidentes, y recibir notificaciones por email cuando se produce un baneo. Al final tendrás una capa de defensa activa que trabaja sola mientras duermes.
Instalar Fail2ban
La instalación en Ubuntu es directa a través de apt. Fail2ban está en los repositorios oficiales y funciona sin dependencias adicionales:
sudo apt update
sudo apt install fail2ban -y
# Verificar que está corriendo
sudo systemctl status fail2ban
sudo systemctl enable fail2ban
Después de instalar, Fail2ban arranca automáticamente pero sin jails activos (excepto el de SSH por defecto en algunas versiones). Lo vamos a configurar desde cero para tener control total.
Antes de tocar nada, es importante entender la estructura de archivos de configuración:
/etc/fail2ban/jail.conf— configuración por defecto que incluye el paquete. Nunca edites este archivo directamente. Se sobreescribe en cada actualización de Fail2ban./etc/fail2ban/jail.local— tu configuración personalizada. Este archivo sobreescribe los valores dejail.conf. Aquí es donde trabajamos./etc/fail2ban/filter.d/— directorio con filtros regex para cada servicio. Cada filtro define qué patrones en los logs indican un intento fallido./etc/fail2ban/action.d/— directorio con acciones que se ejecutan al banear (iptables, nftables, envío de email, etc.).
Proteger SSH
El primer jail que debes configurar es el de SSH. Es el servicio más atacado en cualquier servidor y el que puede tener consecuencias más graves si alguien logra acceder.
Crea el archivo jail.local:
sudo nano /etc/fail2ban/jail.local
Añade el siguiente contenido:
[DEFAULT]
# IP de tu oficina/casa — NUNCA SE BANEA (ajustar)
ignoreip = 127.0.0.1/8 ::1
# Tiempo de ban: 1 hora (3600s) para empezar
bantime = 3600
# Ventana de tiempo para contar intentos: 10 minutos
findtime = 600
# Máximo de intentos antes del ban
maxretry = 3
# Backend de detección
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
ignoreip ANTES de activar Fail2ban. Si te baneas a ti mismo, necesitarás acceso a la consola de rescate de tu VPS. Puedes añadir múltiples IPs separadas por espacio: ignoreip = 127.0.0.1/8 ::1 TU_IP_FIJA. Si tu IP es dinámica, considera añadir el rango de tu ISP o aumentar maxretry a 10 para darte más margen.
Aplica la configuración y verifica que el jail SSH está activo:
sudo systemctl restart fail2ban
# Ver status del jail SSH
sudo fail2ban-client status sshd
# Ejemplo de output:
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 2
# | |- Total failed: 847
# | `- File list: /var/log/auth.log
# `- Actions
# |- Currently banned: 3
# |- Total banned: 12
# `- Banned IP list: 185.220.101.5 45.33.32.156 209.141.55.28
Si ves "Currently failed" aumentando y "Total failed" con cientos de entradas, es señal de que Fail2ban está leyendo los logs correctamente y detectando intentos. Las IPs en "Banned IP list" son las que ya están siendo bloqueadas por iptables.
Proteger nginx
Nginx también está expuesto a ataques automatizados: intentos de fuerza bruta contra paneles de admin protegidos con HTTP auth, scanners buscando vulnerabilidades conocidas, y bots que martillan endpoints generando carga innecesaria. Fail2ban tiene filtros predefinidos para nginx que puedes activar con pocas líneas.
Jail para autenticación HTTP
Si proteges algún directorio o panel con autenticación HTTP básica de nginx, este jail banea a quien falla repetidamente:
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
Jail para bots y scanners
Este jail detecta bots que buscan rutas conocidas de exploits, instalaciones de WordPress vulnerables, paneles de admin por defecto y similares. Con maxretry = 2 y findtime = 60 baneamos rápido y por 24 horas:
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
findtime = 60
Jail para rate limiting
Si tienes configurado rate limiting en nginx con limit_req_zone, puedes hacer que Fail2ban también banee a nivel de firewall a las IPs que lo disparan repetidamente. Esto añade una capa extra cuando el rate limiting de nginx por sí solo no es suficiente:
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 3600
findtime = 60
Después de añadir los jails de nginx, recarga Fail2ban y verifica que están activos:
sudo systemctl reload fail2ban
sudo fail2ban-client status
El segundo comando lista todos los jails activos. Deberías ver sshd, nginx-http-auth, nginx-botsearch y nginx-limit-req en la lista.
Ban permanente para reincidentes
Una IP que recibe un ban, espera a que expire, y vuelve a atacar no es un usuario legítimo que cometió un error de tipeo. Es un bot o un atacante persistente. Para estos casos existe el jail recidive: monitorea el propio log de Fail2ban y banea por períodos mucho más largos a las IPs que acumulan baneos repetidos.
Añade al final de tu jail.local:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = 604800 ; 1 semana
findtime = 86400 ; buscar en las últimas 24 horas
maxretry = 3 ; banear si fue baneado 3 veces en 24h
El parámetro banaction = iptables-allports bloquea la IP en todos los puertos, no solo en el del servicio que activó el ban original. Una IP reincidente no merece acceso a nada en tu servidor.
Una nota sobre los baneos permanentes: aunque pueda parecer la opción más agresiva, los baneos permanentes no son recomendables a largo plazo. Las IPs atacantes se acumulan en la tabla de iptables, que crece sin límite y empieza a afectar el rendimiento del sistema. Un ban de una semana cubre la gran mayoría de campañas de ataque y permite que la tabla se mantenga a un tamaño manejable. Si quieres más permanencia, considera integrar listas de reputación de IPs como fail2ban-blacklist.
sudo systemctl reload fail2ban
# Verificar que recidive está activo
sudo fail2ban-client status recidive
Notificaciones por email
Saber cuándo tu servidor banea una IP es útil para entender el nivel de actividad que estás manejando. Fail2ban puede enviar emails con detalles del ban incluyendo información whois de la IP atacante y las líneas del log que activaron el ban.
Instalar msmtp
Fail2ban usa el comando mail o un MTA local para enviar emails. La forma más limpia en un VPS es instalar msmtp, un cliente SMTP ligero que enruta el correo a través de un servidor externo (Gmail, SendGrid, etc.) sin necesidad de configurar un MTA completo:
sudo apt install msmtp msmtp-mta -y
Crea el archivo de configuración /etc/msmtprc:
# /etc/msmtprc
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
account gmail
host smtp.gmail.com
port 587
from tu-email@gmail.com
user tu-email@gmail.com
password tu-app-password
account default : gmail
Reemplaza tu-email@gmail.com y tu-app-password con tus credenciales. Si usas Gmail, necesitas generar una App Password desde la configuración de seguridad de tu cuenta (no puedes usar tu contraseña normal si tienes 2FA activado, que deberías tener). Asegúrate de que el archivo tenga permisos restrictivos:
sudo chmod 600 /etc/msmtprc
Configurar Fail2ban para enviar emails
Añade o modifica el bloque [DEFAULT] en tu jail.local para incluir la configuración de email:
[DEFAULT]
# Agregar al bloque DEFAULT existente
destemail = tu@email.com
sender = fail2ban@tu-servidor.com
mta = msmtp
action = %(action_mwl)s
El parámetro action define qué hace Fail2ban además del ban. Hay tres niveles:
%(action_)s— solo banea (sin email)%(action_mw)s— banea + envía email con información whois de la IP%(action_mwl)s— banea + email con whois + líneas del log que activaron el ban. El más informativo.
El formato %(action_mwl)s te da contexto completo: sabes qué IP fue baneada, de qué país es, a qué servicio intentó acceder, y exactamente qué líneas de log lo provocaron. Es el más útil para entender qué está pasando en tu servidor.
Comandos del día a día
Estos son los comandos que vas a usar regularmente para gestionar Fail2ban:
# Ver todos los jails activos
sudo fail2ban-client status
# Ver IPs baneadas en un jail específico
sudo fail2ban-client status sshd
# Desbanear una IP manualmente (si te baneaste)
sudo fail2ban-client set sshd unbanip 1.2.3.4
# Ver logs de fail2ban en tiempo real
sudo tail -f /var/log/fail2ban.log
# Probar que un filtro funciona (dry run)
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
# Recargar configuración sin reiniciar
sudo fail2ban-client reload
# Ver estadísticas generales
sudo fail2ban-client stats
Referencia rápida:
| Acción | Comando |
|---|---|
| Ver todos los jails | fail2ban-client status |
| Ver IPs baneadas | fail2ban-client status [jail] |
| Desbanear IP | fail2ban-client set [jail] unbanip [IP] |
| Recargar config | fail2ban-client reload |
| Ver logs | tail -f /var/log/fail2ban.log |
Errores comunes
Me baneé a mí mismo
El error más frecuente al configurar Fail2ban. Sucede cuando no añadiste tu IP a ignoreip y fallaste el login el número de veces configurado en maxretry. Prevención: siempre añade tu IP a ignoreip antes de activar cualquier jail.
Si ya ocurrió y tienes acceso por VNC o consola web desde el panel de tu proveedor VPS:
sudo fail2ban-client set sshd unbanip TU_IP
Si no tienes acceso por consola, contacta al soporte de tu proveedor VPS pidiendo acceso de emergencia a la consola. La mayoría de proveedores (DigitalOcean, Hetzner, Vultr, Linode) ofrecen consola web desde su panel de control. Esto es otra razón por la que deberías familiarizarte con esa opción antes de que la necesites en una emergencia.
Fail2ban no arranca después de cambios en jail.local
Un error de sintaxis en jail.local impide que Fail2ban arranque. Antes de reiniciar, siempre valida la configuración:
# Ver errores detallados
sudo systemctl status fail2ban -l
sudo journalctl -u fail2ban -n 50
# El error más común: sintaxis incorrecta en jail.local
# Verificar la configuración antes de reiniciar
sudo fail2ban-client -t
El comando fail2ban-client -t valida la configuración sin aplicarla. Si hay errores de sintaxis, los muestra aquí antes de que rompas el servicio activo. Úsalo siempre antes de reload o restart.
Los logs no están en la ruta esperada
Diferentes distribuciones y versiones de Ubuntu ubican los logs en rutas distintas. Si el logpath que configuraste no existe, el jail no funciona:
# Ubuntu 20.04+ con systemd: usar backend = systemd
# Logs en: journalctl -u sshd
# Ubuntu 18.04 y Debian: /var/log/auth.log
# CentOS/RHEL: /var/log/secure
# nginx: /var/log/nginx/error.log (ajustar si tienes logs por vhost)
En Ubuntu 20.04 y versiones posteriores, si usas backend = systemd en el bloque DEFAULT, Fail2ban lee directamente del journal en lugar de un archivo de log. Esto es más confiable y no depende de que el archivo de log exista. Para nginx con múltiples vhosts, puede que necesites ajustar logpath para que apunte al log específico de cada vhost donde tienes la autenticación HTTP.
Fail2ban no está baneando aunque ve intentos
Si fail2ban-client status sshd muestra "Currently failed" aumentando pero ninguna IP se banea, el filtro puede no estar capturando las líneas correctamente:
# Verificar que el filtro está detectando líneas
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf | tail -20
# Si muestra 0 matches, el problema es el filtro
# Ver versión de sshd y comparar con el formato de log esperado
sudo sshd -V
Si fail2ban-regex muestra 0 matches, el filtro no está capturando las líneas de tu versión de sshd. El formato de log puede variar entre versiones. En ese caso, revisa el filtro en /etc/fail2ban/filter.d/sshd.conf y compara los patrones regex con el formato real de tus logs. También puede ocurrir si usas autenticación por clave SSH exclusivamente (sin contraseña), en cuyo caso no habrá líneas de "Failed password" que capturar.