Una VPS Linux con SSH accesible desde Internet empieza a recibir tráfico hostil casi inmediatamente.
No hace falta que alguien conozca tu servidor, tu nombre o tu proyecto. Bots distribuidos recorren rangos completos de direcciones IP buscando puertos abiertos y probando combinaciones de usuarios y credenciales comunes.
En una VPS Ubuntu que estaba revisando, un comando muy simple dejó el patrón al descubierto:
sudo journalctl -u ssh --no-pager | grep Inval
El resultado completo era demasiado grande para analizarlo a ojo, así que lo resumimos. Había 9.890 eventos Invalid user conservados en el journal entre el 20 y el 29 de agosto.
Ese dato es una buena excusa para entender tres capas que suelen confundirse:
sshd registra el intento
↓
Fail2Ban detecta abuso y bloquea localmente
↓
AbuseIPDB puede recibir un reporte de esa IP
No son tres alternativas. Son piezas complementarias.
Qué significa Invalid user
Una línea típica era:
Aug 29 04:43:04 oracle-free-vps sshd[253058]: Invalid user admin from 92.118.39.71 port 52480
sshd recibió una conexión que intentó autenticarse como admin, pero esa cuenta no existía en el sistema.
Eso es importante porque un evento Invalid user no demuestra que hayan entrado al servidor. Demuestra que alguien llegó hasta el servicio SSH e intentó usar un nombre de usuario inexistente.
En los datos revisados, los nombres más probados fueron:
1348 admin
573 user
334 test
292 debian
205 deploy
158 oracle
147 ftpuser
133 postgres
124 git
119 administrator
99 testuser
93 guest
82 pi
73 mysql
El patrón es muy reconocible: usuarios genéricos asociados con servidores, bases de datos, despliegues y dispositivos Linux.
No parece que el atacante conozca tus cuentas. Está lanzando un diccionario de nombres frecuentes.
Tampoco era una sola IP
Las IP con más eventos fueron:
445 95.173.161.147
248 66.116.205.1
248 45.234.176.26
212 103.159.51.70
209 193.32.162.34
189 92.118.39.49
181 92.118.39.77
157 101.36.104.242
148 64.227.168.220
148 195.178.110.228
Esto también es típico del scanning de Internet actual: no necesariamente existe un único origen que puedas bloquear manualmente y olvidar.
Puedes añadir una IP al firewall, pero mañana aparecerán otras cien.
Necesitas automatizar la respuesta.
Antes de bloquear: busca lo realmente importante
Filtrar por Invalid user es útil para entender el ruido, pero no responde la pregunta más importante: ¿hubo intentos contra usuarios válidos o autenticaciones exitosas?
El siguiente filtro debería ser algo como:
sudo journalctl -u ssh --no-pager \
| grep -E 'Failed password|Accepted password|Accepted publickey'
Las diferencias importan:
Invalid user
→ intentaron una cuenta que no existe
Failed password
→ hubo una autenticación fallida
Accepted password
→ una contraseña fue aceptada
Accepted publickey
→ una clave pública fue aceptada
Por eso miles de eventos Invalid user pueden ser puro ruido de Internet, mientras que una sola autenticación exitosa desde una IP inesperada merece mucha más atención.
El problema de bloquear IPs a mano
Podríamos hacer algo como:
sudo ufw deny from 95.173.161.147
Pero no escala.
En nuestro pequeño conjunto ya había muchas IP distintas. Además, una IP puede atacar durante unos minutos, desaparecer y ser sustituida por otra.
Aquí entra Fail2Ban.
Qué hace realmente Fail2Ban
Fail2Ban observa logs, identifica patrones de abuso y mantiene contadores por IP.
Conceptualmente:
logs de sshd
↓
Fail2Ban
↓
¿esta IP superó el número permitido de fallos?
↓ sí
firewall
↓
bloqueo temporal de la IP
La guía oficial de AbuseIPDB describe Fail2Ban de la misma manera: analiza logs como los de autenticación y, cuando una dirección genera demasiados fallos, actualiza reglas del firewall para rechazar nuevas conexiones durante un tiempo configurable.
En Ubuntu podemos empezar comprobando su estado:
sudo systemctl status fail2ban
Y las jails activas:
sudo fail2ban-client status
Para SSH:
sudo fail2ban-client status sshd
Una jail combina fundamentalmente:
qué logs/patrones observar
cuántos fallos tolerar
qué ventana temporal usar
cuánto tiempo bloquear
qué acción ejecutar al banear
Fail2Ban y AbuseIPDB no hacen lo mismo
Esta separación es crucial.
Fail2Ban hace el enforcement local.
Cuando detecta abuso puede modificar el firewall del servidor y evitar que esa IP siga conectándose durante el bantime.
AbuseIPDB es una base colaborativa de reputación de direcciones IP.
La integración permite que, cuando Fail2Ban banee una IP, ejecute también una acción que la reporte mediante la API de AbuseIPDB.
El flujo completo queda así:
1. Bot intenta entrar por SSH
2. sshd genera eventos
3. Fail2Ban reconoce el patrón
4. Fail2Ban banea la IP localmente
5. La acción abuseipdb reporta esa IP
6. El reporte contribuye a la reputación compartida
AbuseIPDB no sustituye el bloqueo de Fail2Ban en este flujo. Añade inteligencia colaborativa y reporting.
La integración ya viene preparada en Fail2Ban moderno
Según la documentación de AbuseIPDB, el soporte para esta acción está en Fail2Ban desde la versión 0.10.0.
Podemos comprobar la versión instalada:
fail2ban-client -V
Y verificar que exista la acción:
ls -l /etc/fail2ban/action.d/abuseipdb.conf
Además necesitamos una API key de AbuseIPDB.
Es mejor mantener nuestras modificaciones en:
/etc/fail2ban/jail.local
en lugar de editar directamente la configuración distribuida por el paquete.
Activar el reporte para la jail de SSH
La documentación oficial muestra una configuración de este estilo:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
action = %(action_)s
%(action_abuseipdb)s[abuseipdb_apikey="TU_API_KEY", abuseipdb_category="18,22"]
Las categorías usadas aquí son:
18 = Brute-Force
22 = SSH
Eso permite que el ban normal siga ocurriendo y, adicionalmente, la dirección sea reportada como abuso relacionado con brute force sobre SSH.
Después de modificar la configuración:
sudo fail2ban-client reload
Y podemos observar su actividad:
sudo tail -f /var/log/fail2ban.log
¿Qué envía la acción a AbuseIPDB?
La acción estándar utiliza la API de reportes de AbuseIPDB y envía, entre otros datos:
IP baneada
categoría del abuso
comentario basado en el evento que disparó el ban
La implementación se encuentra en:
/etc/fail2ban/action.d/abuseipdb.conf
Es importante revisar ese archivo antes de activarlo si no quieres que el fragmento del log aparezca como comentario del reporte.
La propia documentación explica que el comentario predeterminado puede incluir el match del log que provocó el ban.
Cuidado con los límites y los reportes duplicados
La API no debe tratarse como un sumidero infinito de eventos.
AbuseIPDB indica que aplica límites diarios de reportes según el tipo de cuenta y, además, evita que la misma IP sea reportada repetidamente dentro de una ventana de 15 minutos.
Por eso recomienda que el bantime no sea inferior a esa ventana cuando utilices esta integración. Un valor de 900 segundos representa 15 minutos:
bantime = 900
En la práctica yo no escogería los valores de bantime, findtime y maxretry copiando números a ciegas. Primero observaría el patrón real de tráfico y el riesgo de bloquear usuarios legítimos.
Fail2Ban es una capa, no toda la seguridad de SSH
Ver 9.890 intentos hostiles puede llevar a pensar que Fail2Ban resolverá todo.
No.
La defensa debería ser por capas:
Internet
│
▼
reglas de red / firewall cloud
│
▼
firewall del host
│
▼
sshd endurecido
│
▼
Fail2Ban
│
├── bloquea automáticamente
│
└── AbuseIPDB reporta reputación
│
▼
logs + auditoría + alertas
Para un servidor administrado mediante claves SSH, medidas especialmente importantes son:
usar autenticación por clave pública
no permitir login directo de root
restringir SSH en la capa de red cuando sea viable
revisar autenticaciones exitosas, no solo fallidas
mantener OpenSSH actualizado
Si puedes restringir el puerto SSH para que solo determinadas redes lleguen a él, eso es mejor que dejar que cada scanner del planeta alcance sshd y confiar después en Fail2Ban.
Cambiar el puerto SSH no es la solución principal
Mover SSH del puerto 22 a otro puerto puede reducir muchísimo el ruido de bots que escanean únicamente puertos comunes.
Pero no convierte el servicio en seguro.
Un scanner que haga descubrimiento de puertos seguirá encontrándolo.
Es una técnica para reducir ruido operativo, no una frontera de seguridad equivalente a:
claves públicas
restricciones de red
firewall
configuración segura de sshd
Un pequeño pipeline de detección útil
Para investigar rápidamente un servidor expuesto podemos empezar así.
Ver intentos contra usuarios inexistentes:
sudo journalctl -u ssh --no-pager | grep 'Invalid user'
Contarlos:
sudo journalctl -u ssh --no-pager \
| grep 'Invalid user' \
| wc -l
Encontrar las IP más frecuentes:
sudo journalctl -u ssh --no-pager \
| grep 'Invalid user' \
| sed -n 's/.* from \([^ ]*\) port.*/\1/p' \
| sort \
| uniq -c \
| sort -nr \
| head
Encontrar los usuarios más probados:
sudo journalctl -u ssh --no-pager \
| grep 'Invalid user' \
| sed -n 's/.*Invalid user \([^ ]*\) from.*/\1/p' \
| sort \
| uniq -c \
| sort -nr \
| head
Y revisar autenticaciones relevantes:
sudo journalctl -u ssh --no-pager \
| grep -E 'Failed password|Accepted password|Accepted publickey'
Después, Fail2Ban puede convertir ese análisis manual en una respuesta automática.
La idea que me llevo
El descubrimiento interesante no son realmente las 9.890 líneas del journal.
Es este cambio de perspectiva:
antes:
veo una IP mala → la bloqueo manualmente
mejor:
observo un comportamiento → detecto el patrón → respondo automáticamente
mejor todavía:
respondo localmente → comparto la señal de abuso
journald nos da evidencia.
Fail2Ban convierte esa evidencia en una acción defensiva.
AbuseIPDB permite convertir el ban local en una señal que también puede servir a otros administradores.
Ese es un ejemplo pequeño pero muy claro de defensa automatizada y colaborativa.