fail2ban baneaba a Cloudflare: la IP real en nginx
El 7 de septiembre miré la lista de direcciones que fail2ban tenía bloqueadas en mi servidor y conté 121. De esas 121, 119 eran nodos de Cloudflare. No estaba parando a ningún atacante: estaba dejando fuera, por turnos, a los lectores de media Europa.
La causa era tonta y llevaba meses ahí. Todos mis dominios pasan por Cloudflare, así que quien llama a nginx no es el visitante, es un nodo del borde. nginx apuntaba esa dirección en el log, fail2ban leía el log y baneaba al nodo. El atacante volvía por otro nodo y seguía.

Qué voy a hacer y en qué máquina
Un servidor Ubuntu con Plesk, nginx 1.30 delante de PHP-FPM y unos treinta dominios, todos detrás de Cloudflare. fail2ban trae la cárcel plesk-wordpress, que lee los logs de acceso de cada dominio y bloquea a quien falla cinco veces contra wp-login.php.
Voy a decirle a nginx que, cuando la petición venga de un rango de Cloudflare, tome la dirección real de la cabecera CF-Connecting-IP. Es una configuración de servidor, no de dominio: afecta a los treinta vhosts a la vez, y a todos les viene bien.
[WARN] Solo hay que confiar en la cabecera cuando la petición viene de un rango de Cloudflare. Si se confía en cualquiera, alguien que ataque el servidor directamente puede escribir una CF-Connecting-IP inventada y saltarse los baneos y los límites de peticiones.
[INFO] Los dominios que no pasan por Cloudflare no se ven afectados: sus peticiones no llegan desde esos rangos y la regla no se aplica.
1. Comprobar que nginx sabe hacerlo
nginx -V 2>&1 | grep -o 'with-http_realip_module'
sudo fail2ban-client status plesk-wordpress | grep -i banned
La primera línea tiene que devolver el nombre del módulo; el nginx de Plesk lo trae compilado. La segunda enseña cuántas direcciones hay bloqueadas ahora mismo. Apunto el número: es el que voy a comparar después.
2. Escribir la configuración
Un fichero nuevo en conf.d, con un nombre que lo ordene antes que el resto. Los rangos son los que publica Cloudflare en cloudflare.com/ips; los copié el 7 de septiembre de 2026.
# Rangos de Cloudflare (IPv4 e IPv6), tomados de cloudflare.com/ips
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
# La cabecera con la IP de origen. Mejor que X-Forwarded-For:
# lleva una sola direccion, no una lista que se pueda alargar.
real_ip_header CF-Connecting-IP;
set_real_ip_from marca esos rangos como intermediarios de confianza y real_ip_header dice de qué cabecera sacar la dirección de verdad. nginx solo mira la cabecera si la petición viene de uno de esos rangos, que es lo que evita el aviso de arriba.
3. Probar antes de recargar
sudo nginx -t
sudo systemctl reload nginx
Recargar, no reiniciar: con reload nginx lee la configuración nueva sin cortar las conexiones abiertas. Si nginx -t se queja, no se recarga nada y se vuelve al paso anterior.
Comprobación
# Las ultimas peticiones ya no deberian venir de rangos de Cloudflare
sudo tail -n 20 /var/www/vhosts/system/midominio.com/logs/proxy_access_ssl_log | cut -d' ' -f1
# Las direcciones que fail2ban vaya bloqueando a partir de ahora
sudo fail2ban-client status plesk-wordpress | grep -i 'banned ip'
Antes del cambio, las veinte líneas del log empezaban por direcciones de los rangos de arriba. Después, por direcciones de gente. Los baneos antiguos caducan solos: mi cárcel los mantiene una hora. Si hace falta soltar uno a mano, fail2ban-client set plesk-wordpress unbanip seguido de la dirección.
Hay un efecto secundario que no busqué y agradezco: el límite de peticiones por dirección que tengo en nginx para este sitio también contaba a todos los visitantes de un nodo como uno solo. Ahora cuenta a cada uno por separado.
Vuelta atrás
sudo mv /etc/nginx/conf.d/aa100_cloudflare_real_ip.conf /root/aa100_cloudflare_real_ip.conf.off
sudo nginx -t && sudo systemctl reload nginx
Mover el fichero fuera de conf.d y recargar devuelve nginx a como estaba. Lo guardo fuera en vez de borrarlo: volver a copiar veinte rangos a mano es la clase de tarea en la que me equivoco.
Qué no he probado
- Apache. En mi servidor casi nada pasa por él, pero la cárcel también lee sus logs y ahí seguiría entrando la dirección del nodo. No lo he medido.
- Un servidor sin Plesk. Las rutas del log y el nombre de la cárcel son los de Plesk; las dos directivas de nginx son las mismas en cualquier Ubuntu.
- Mantener la lista al día. Al escribir esta guía he vuelto a mirar la página de Cloudflare y tiene dos rangos que mi fichero del 7 de septiembre no tiene. El comentario del fichero ya avisaba de revisarla una vez al año; no pensé que fuera a hacer falta a las dos semanas.
Esto encaja con dos guías anteriores: la lista de seguridad inicial de Ubuntu Server, donde fail2ban entra sin este matiz, y la de Cloudflare Tunnel, que tiene el mismo problema si el servicio de detrás registra direcciones. Referencia: la documentación de nginx sobre ngx_http_realip_module.
0 comentarios
Sé concreto, añade contexto (versión, distro, stack) y si puedes pega logs en bloque de código. Menos drama, más señales.