Security 5 min de lectura 25 de febrero de 2026

Configuración avanzada de Fail2Ban y CrowdSec para WordPress

Optimiza la seguridad de tu servidor integrando logs de Apache con herramientas de prevención de intrusiones.

Solución en "Dos Clics" (TL;DR)

Configuración y optimización de Fail2Ban y CrowdSec para proteger un servidor web Apache con WordPress.

Administrar un VPS de producción expuesto a internet implica transicionar rápidamente de una configuración básica de servidor a una postura activa de endurecimiento y auditoría constante de seguridad.

Durante una revisión rutinaria de la infraestructura en DOSCLIC, un análisis detallado de los registros de acceso y error de Apache reveló un volumen masivo de escaneos automatizados, intentos de fuerza bruta y patrones orientados a vulnerar aplicaciones web, particularmente instalaciones de WordPress y sistemas legacy basados en Perl como Koha. Este artículo documenta el proceso completo de diagnóstico, los intentos de contención con Fail2Ban, la identificación de vectores de ataque reales y la adopción de una arquitectura de defensa híbrida junto a CrowdSec.

1. Contexto del Entorno y Análisis Inicial de Logs

El servidor opera sobre Ubuntu Server 22.04.5 LTS con Virtualmin como panel de administración web, ejecutando múltiples sitios virtuales que combinan PHP nativo, Laravel y WordPress. La versión de Fail2Ban en uso es la 0.11.2.

El primer desafío técnico surgió al estructurar los filtros de Fail2Ban para múltiples dominios. Inicialmente se consideró utilizar la ruta genérica basada en enlaces simbólicos proporcionada por algunos tutoriales:

Configuración genérica descartada
logpath = /home/*/logs/access_log

Sin embargo, al inspeccionar el sistema de archivos del servidor, se comprobó que dichas rutas eran simples enlaces simbólicos que apuntaban a los registros reales generados por Virtualmin:

Terminal (SSH)
lrwxrwxrwx 1 ucm ucm 41 Jul 25  2024 /home/ucm/logs/access_log -> /var/log/virtualmin/ucm.edu.ni_access_log

Se determinó que es significativamente más limpio, eficiente y escalable apuntar de manera directa a los registros reales ubicados en el directorio de Virtualmin, evitando la dependencia de enlaces simbólicos que podrían romperse ante modificaciones estructurales de los usuarios:

Configuración óptima
logpath = /var/log/virtualmin/*_access_log

2. El Proceso de Auditoría y Detección de Ruido en Internet

Para medir el comportamiento real del tráfico malicioso frente al legítimo, se implementó una rutina de análisis utilizando herramientas nativas de Linux (awk, sort, uniq y grep) sobre los archivos de registro diarios unificados:

Terminal (SSH) - Extracción de IPs más activas
awk '{print $1}' dia.log | sort | uniq -c | sort -nr | head -50

Los resultados confirmaron que aproximadamente el 90% del tráfico correspondía a escaneos automatizados buscando archivos sensibles como /.env, /.git/config, /wp-login.php, /xmlrpc.php y nombres aleatorios de webshells ejecutables en PHP (g.php, az.php, ioxi.php).

Error detectado: Durante la revisión de respuestas HTTP 200 en rutas internas de WordPress (como /wp-includes/assets/index.php), se identificaron patrones que inicialmente encendieron alertas de posible compromiso, requiriendo una verificación exhaustiva de la integridad del sistema de archivos.

Para descartar modificaciones no autorizadas en el núcleo de WordPress, se ejecutó una validación mediante WP-CLI:

Terminal (SSH)
wp core verify-checksums

3. El Punto de Quiebre: Compromiso Histórico por XML-RPC

A pesar de contar con Fail2Ban activo, un análisis más exhaustivo de los registros históricos reveló un evento crítico ocurrido semanas atrás. El servidor había sido vulnerado a través de ataques de fuerza bruta masiva utilizando solicitudes POST masivas contra el endpoint /xmlrpc.php mediante el método system.multicall de WordPress.

Este vector permitió a los atacantes obtener credenciales válidas sin dejar rastros de modificaciones en el núcleo de archivos PHP (descartando webshells tradicionales), procediendo a inyectar miles de publicaciones de spam de contenido relacionado con casinos y apuestas para secuestrar la autoridad SEO de los dominios afectados.

4. Soluciones Aplicadas y Hardening del Servidor

Para neutralizar los vectores de ataque descubiertos y endurecer la postura defensiva general del VPS, se implementó un conjunto de contramedidas directas a nivel de servidor web y de análisis de comportamiento:

Bloqueo Definitivo de XML-RPC en Apache

Dado que los sitios alojados no utilizan aplicaciones móviles ni funciones remotas dependientes de XML-RPC, se bloqueó el acceso al archivo en cada VirtualHost de forma definitiva:

Configuración VirtualHost Apache
<FilesMatch "xmlrpc\.php$">
    Require all denied
</FilesMatch>

Protección de Archivos Sensibles y Ocultos

Se implementó una directiva global en Apache para denegar el acceso a cualquier archivo o directorio oculto (que comience con un punto, como .env o .git):

Archivo security-hardening.conf
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

Optimización y Endurecimiento de Jails en Fail2Ban

Se ajustaron los parámetros temporales y se crearon filtros personalizados más agresivos para capturar códigos de redirección sospechosos (302) asociados a escaneos de archivos PHP inexistentes y peticiones a .env:

Extracto de jail.local
[wordpress-hard]
enabled  = true
filter   = wordpress-hard
logpath  = /var/log/virtualmin/*_access_log
findtime = 10m
maxretry = 5
bantime  = 7d

Instalación de mod_evasive para Rate Limiting

Para mitigar ataques de denegación de servicio a nivel de aplicación y frenar la velocidad de los bots antes de que impacten los registros o saturen los recursos de PHP-FPM, se configuró mod_evasive en Apache:

Extracto de evasive.conf
<IfModule mod_evasive20.c>
    DOSHashTableSize 3097
    DOSPageCount 20
    DOSPageInterval 1
    DOSSiteCount 100
    DOSSiteInterval 1
    DOSBlockingPeriod 3600
</IfModule>

5. Transición Híbrida hacia CrowdSec

Ante la necesidad de contar con inteligencia colaborativa global y una detección basada en comportamiento más avanzada, se procedió a instalar CrowdSec en paralelo, manteniendo Fail2Ban para la protección específica de servicios locales como SSH y Webmin.

Durante el proceso de configuración, se resolvieron conflictos iniciales de puertos con la API local (LAPI) reubicándola del puerto 8080 (ocupado por Apache) al puerto 8081 en /etc/crowdsec/config.yaml y local_api_credentials.yaml. Asimismo, se incorporaron las direcciones IP de administración en la lista blanca local para evitar bloqueos accidentales durante la etapa de pruebas en modo detección.

Consejo Práctico: Siempre instala herramientas de análisis avanzado como CrowdSec en modo de solo detección inicial (sin bouncers activos conectados al firewall), permitiendo auditar durante un par de días las alertas generadas y validar las listas blancas antes de aplicar bloqueos automáticos en producción.
La historia detrás de la nota

Creer que un servidor pequeño o de nicho pasa desapercibido en internet es un error de confianza; la auditoría activa de registros y la defensa por capas son el único camino real hacia la tranquilidad técnica.