Limitación de velocidad en Nginx: añadir rate limit para solicitudes de escaneo 404 y 400 de alta frecuencia

Guía práctica de limit_req y limit_conn en Nginx: cómo limitar rutas sospechosas de escaneo, solicitudes 404/400 frecuentes y concurrencia por IP, con notas sobre la ubicación correcta de limit_req_zone y los parámetros habituales.

Si los logs de un sitio empiezan a mostrar una gran cantidad de respuestas 404 y 400, muchas veces no se debe a usuarios haciendo clic en enlaces rotos. Lo habitual es que un escáner automático esté probando rutas como .env, .git, wp-admin, phpmyadmin o xmlrpc.php.

Estas solicitudes generan varios problemas:

  • el access log crece rápido;
  • el error log se llena de ruido inútil;
  • sitios estáticos o servicios de proxy inverso desperdician conexiones en solicitudes inválidas;
  • los problemas reales quedan enterrados bajo ruido de escaneo.

Nginx puede controlar esto con limit_req y limit_conn. Pero hay un punto importante: Nginx no puede limitar directamente por “el código de respuesta será 404 o 400”, porque el rate limit se aplica antes de generar la respuesta.

La práctica correcta es limitar por adelantado las rutas de escaneo, orígenes sospechosos y solicitudes globales de alta frecuencia que normalmente terminan produciendo 404 / 400.

Idea básica

Conviene dividirlo en tres capas:

  1. Aplicar un límite suave para todo el sitio y evitar que una sola IP golpee demasiado.
  2. Aplicar un límite estricto a rutas comunes de escaneo y devolver 404 directamente.
  3. Limitar conexiones concurrentes por IP.

Una forma segura de desplegarlo es empezar con reglas de rutas de escaneo y access_log off, observar un día y, si todavía hay muchas rutas aleatorias con 404, añadir limit_req global.

Definir primero las zonas en http

limit_req_zone y limit_conn_zone deben estar dentro de http {}. No pueden colocarse dentro del server {} de un sitio concreto.

Puedes añadirlas al bloque http {} de /etc/nginx/nginx.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
http {
    # Rate limit por IP de cliente para páginas normales
    limit_req_zone $binary_remote_addr zone=perip_general:20m rate=5r/s;

    # Más estricto para rutas sospechosas de escaneo
    limit_req_zone $binary_remote_addr zone=perip_scan:20m rate=1r/s;

    # Límite de conexiones concurrentes
    limit_conn_zone $binary_remote_addr zone=addr_conn:20m;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

También puedes crear un archivo nuevo:

1
sudo nano /etc/nginx/conf.d/limit-zones.conf

Con este contenido:

1
2
3
limit_req_zone $binary_remote_addr zone=perip_general:20m rate=5r/s;
limit_req_zone $binary_remote_addr zone=perip_scan:20m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr_conn:20m;

Esto presupone que tu nginx.conf incluye dentro de http {}:

1
include /etc/nginx/conf.d/*.conf;

Usar las zonas dentro de server

La configuración de un sitio suele estar en algo como /etc/nginx/sites-enabled/www.example.com y normalmente contiene un bloque server {}. Ahí no debes volver a escribir limit_req_zone; solo se usan las zonas ya definidas.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server {
    root /srv/www/example.com;
    index index.html;
    server_name example.com www.example.com;
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    # Límite suave para todo el sitio, permitiendo ráfagas cortas
    limit_req zone=perip_general burst=30 nodelay;
    limit_conn addr_conn 20;

    # Rutas comunes de escaneo: límite estricto y sin access log
    location ~* ^/(\.env|\.git|\.svn|wp-|wp/|adminer|phpmyadmin|pma|vendor|backup|config|server-status|cgi-bin|xmlrpc\.php) {
        access_log off;
        limit_req zone=perip_scan burst=5 nodelay;
        return 404;
    }

    # El resto de location /, listen ssl y demás configuración va aquí
}

Si te preocupa afectar tráfico normal con el límite global, empieza solo con la regla de rutas de escaneo:

1
2
3
4
5
location ~* ^/(\.env|\.git|\.svn|wp-|wp/|adminer|phpmyadmin|pma|vendor|backup|config|server-status|cgi-bin|xmlrpc\.php) {
    access_log off;
    limit_req zone=perip_scan burst=5 nodelay;
    return 404;
}

Qué significan estos parámetros

Esta línea:

1
limit_req_zone $binary_remote_addr zone=perip_general:20m rate=5r/s;

significa:

  • limit_req_zone: define la zona de contabilidad para limitar solicitudes.
  • $binary_remote_addr: usa la IP del cliente como clave y consume menos memoria que $remote_addr.
  • zone=perip_general:20m: crea una zona de memoria compartida llamada perip_general de 20m.
  • rate=5r/s: cada IP puede hacer, en promedio, 5 solicitudes por segundo.

Esta línea:

1
limit_req_zone $binary_remote_addr zone=perip_scan:20m rate=1r/s;

es similar, pero más estricta:

  • perip_scan: zona dedicada a rutas sospechosas de escaneo.
  • rate=1r/s: cada IP solo puede hacer 1 solicitud por segundo.

Para conexiones concurrentes:

1
2
limit_conn_zone $binary_remote_addr zone=addr_conn:20m;
limit_conn addr_conn 20;

Esto limita cada IP a un máximo de 20 conexiones simultáneas.

Cómo entender burst y nodelay

Ejemplo:

1
limit_req zone=perip_general burst=30 nodelay;

Se puede leer así:

  • rate=5r/s: la tasa media a largo plazo es de 5 solicitudes por segundo.
  • burst=30: se permiten 30 solicitudes extra en una ráfaga corta.
  • nodelay: si se supera la tasa media pero no el burst, Nginx procesa inmediatamente; solo rechaza cuando se supera el burst.

Sin nodelay, Nginx intenta retrasar y encolar parte de las solicitudes. Para páginas normales, nodelay suele ser más fácil de razonar. Para APIs sensibles, conviene ajustar según el comportamiento real.

Error común: limit_req_zone en el lugar equivocado

Si ves un error como:

1
2026/04/30 21:33:48 [emerg] 2290771#2290771: "limit_req_zone" directive is not allowed here in /etc/nginx/sites-enabled/example.com:9

significa que limit_req_zone está en un contexto no permitido.

Un error típico es ponerlo dentro de server {}:

1
2
3
4
5
6
7
8
9
server {
    root /srv/www/example.com;
    index index.html;
    server_name example.com www.example.com;

    limit_req_zone $binary_remote_addr zone=perip_general:20m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=perip_scan:20m rate=1r/s;
    limit_conn_zone $binary_remote_addr zone=addr_conn:20m;
}

Esto no funciona.

Regla sencilla:

  • limit_req_zone define la zona, va en http {}.
  • limit_req usa la zona, va en server {} o location {}.
  • limit_conn_zone define la zona de conexiones, va en http {}.
  • limit_conn usa la zona de conexiones, va en server {} o location {}.

Bloquear temporalmente IPs claramente anómalas

Si los logs confirman que algunas IPs envían escaneos de forma continua, puedes bloquearlas temporalmente:

1
2
3
4
5
deny 45.95.42.164;
deny 185.177.72.51;
deny 185.177.72.5;
deny 185.177.72.56;
deny 185.177.72.58;

Estas directivas deny pueden ir en server {} o en un location {} concreto. Mantenerlas a largo plazo depende del riesgo de falsos positivos y del origen del tráfico.

Comprobar y recargar

Primero comprueba la configuración:

1
sudo nginx -t

Si no hay errores, recarga:

1
sudo systemctl reload nginx

No reinicies directamente el servicio. reload permite que Nginx cargue la nueva configuración de forma suave y con menos riesgo.

Parámetros recomendados

Para un sitio personal o estático, puedes empezar con:

  • páginas normales: rate=5r/s a 10r/s;
  • rutas de escaneo: rate=1r/s;
  • rutas de escaneo: burst=5;
  • límite global: burst=30;
  • concurrencia por IP: 10 a 20.

Si el tráfico normal es muy bajo, puedes ser más estricto. Si el sitio tiene muchas imágenes, scripts o API, relaja el límite de páginas normales para no afectar visitas reales.

La forma más estable es desplegar por fases:

  1. Aplicar primero access_log off + return 404 a rutas de escaneo.
  2. Añadir después el límite estricto perip_scan.
  3. Observar logs durante un día.
  4. Si siguen apareciendo muchos 404 aleatorios, activar el límite global suave.
记录并分享
Creado con Hugo
Tema Stack diseñado por Jimmy