NGINX se ha convertido en la puerta de acceso de miles de servicios web. Desde esa posición, una de sus funciones más útiles —y con frecuencia mal entendida— es limitar la tasa de peticiones para evitar desbordes, frenar ataques de fuerza bruta y suavizar picos de tráfico que, de otro modo, saturarían las aplicaciones de detrás. Bien configurada, esta capacidad reduce errores 5xx, mantiene a raya los tiempos de respuesta y ayuda a distinguir entre el comportamiento normal de los navegadores (siempre “ráfagas”) y patrones que conviene frenar.
Nota previa importante. La propia comunidad de NGINX ha aclarado que la forma de calcular
burst
ydelay
puede inducir a confusión si se interpreta como promedios rígidos por segundo. NGINX contabiliza a resolución de milisegundo con ventana deslizante, no como media estática a lo largo de un periodo. Para el detalle fino de cada parámetro, conviene revisar la documentación oficial de nginx.org.
La metáfora del “cubo que gotea” aplicada a HTTP
El mecanismo que usa NGINX se basa en el algoritmo de “leaky bucket”: el agua es el stream de peticiones entrantes, el cubo es la cola FIFO y el “goteo” regula a qué ritmo se entregan peticiones al backend. Si entran más de las que pueden salir, el cubo rebosa y hay solicitudes que se rechazan. La gracia está en mantener un flujo estable hacia la aplicación aun cuando el navegador lance varias descargas simultáneas (HTML, CSS, JS, imágenes) o cuando un formulario reciba varios intentos en pocos milisegundos.
En términos prácticos:
- Protección de formularios sensibles: login, reset de contraseña o endpoints de autenticación dejan de ser un coladero para ataques de fuerza bruta.
- Auto-defensa ante picos: una oleada de clics durante una promoción no tiene por qué convertir el backend en una cola infinita.
- Autopsia más clara: con logging, es más sencillo identificar qué URLs sufren abuso o qué clientes generan ráfagas anómalas.
Dos directivas que lo cambian todo: limit_req_zone
y limit_req
La limitación se configura con dos piezas:
limit_req_zone
define la zona compartida de memoria, la clave por la que se agrupan las peticiones y la tasa autorizada. Suele declararse en el bloquehttp
para reutilizarla en variosserver
olocation
.limit_req
aplica la política en el contexto deseado (unlocation
, unserver
, etc.).
Ejemplo mínimo para proteger /login/
a 10 peticiones por segundo por IP:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
Lenguaje del código: PHP (php)
Claves del ejemplo:
- Key:
$binary_remote_addr
agrupa por IP de cliente en binario (ahorra memoria frente a$remote_addr
). - Zone:
mylimit:10m
reserva 10 MiB en memoria compartida; como referencia, 1 MiB almacena ~16.000 direcciones, así que 10 MiB dan para ~160.000 IPs. Si se agota la zona, NGINX purga entradas antiguas; si aun así no cabe la nueva, devuelve 503. - Rate:
10r/s
equivale, de forma aproximada, a 1 petición cada 100 ms. Al medir en milisegundos y con ventana deslizante, se rechaza lo que rompe ese ritmo, salvo que se configure burst.
Ráfagas controladas: burst
para no castigar a los navegadores
Sin burst, dos peticiones separadas por menos de 100 ms (en el ejemplo) harán que la segunda reciba 503. Como la navegación real siempre es “ráfaga”, conviene permitir un colchón:
location /login/ {
limit_req zone=mylimit burst=20;
proxy_pass http://my_upstream;
}
Lenguaje del código: JavaScript (javascript)
burst=20
: habilita 20 “huecos” en cola por encima de la tasa. Las peticiones “demasiado pronto” se encolan y NGINX las libera siguiendo el ritmo marcado (aprox. 1 cada 100 ms en el ejemplo).- Si llegan 21 a la vez, la 1.ª pasa y 20 entran en cola; la 22.ª se rechaza con 503.
Problema: espaciar 20 peticiones a 100 ms significa que la última esperará ~2 s; puede ser inútil para el usuario.
Ráfagas sin añadir latencia: nodelay
Para evitar que la cola introduzca esperas visibles, existe nodelay
:
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
Lenguaje del código: JavaScript (javascript)
- Con
nodelay
, NGINX no “gotea” las peticiones encoladas: las reenvía de inmediato mientras haya “slots” disponibles en el burst y marca esos slots como ocupados durante el intervalo correspondiente (100 ms en el ejemplo). - Si entran 21 de golpe, se envían las 21 al instante y se marcan 20 slots. Cada 100 ms se libera 1.
- Si llegan otras 20 tras 101 ms, solo hay 1 slot libre: 1 se envía, 19 se rechazan.
Efecto neto: se mantiene la tasa efectiva (10 r/s), pero sin añadir retardo a los picos “permitidos”.
Recomendación habitual: usar
burst
+nodelay
salvo que exista una razón concreta para espaciar la cola.
Dos etapas de control: delay
para combinar “pase libre” y “freno suave”
Desde NGINX 1.15.7 se puede definir una primera porción del burst sin demora y, a partir de un umbral, aplicar retraso para no superar la tasa; si se rebasa el burst total, se rechaza. Ejemplo típico:
limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;
server {
location / {
limit_req zone=ip burst=12 delay=8;
proxy_pass http://website;
}
}
Lenguaje del código: PHP (php)
- Tasa base: 5 r/s.
- Ráfaga total: 12.
delay=8
: las primeras 8 por encima de la tasa no se demoran; las siguientes 4 se espacian para cumplir los 5 r/s; a partir de ahí, se rechazan hasta volver a tener “hueco”.
Recuerdo de la nota inicial: la mecánica real opera a milisegundos con ventana deslizante; siempre conviene confirmar el comportamiento exacto en la documentación.
Listas de confianza y segmentación con geo
y map
Es habitual permitir más ritmo a redes internas o socios, y aplicar límites más estrictos al resto. Puede hacerse combinando geo
y map
para construir una clave condicional:
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
server {
location / {
limit_req zone=req_zone burst=10 nodelay;
# ...
}
}
Lenguaje del código: PHP (php)
- Las subredes internas reciben
$limit=0
y, mediantemap
, clave vacía (""
). - Cuando la clave de
limit_req_zone
es cadena vacía, no se aplica el límite. - El resto de IPs usa
$binary_remote_addr
y queda limitado a 5 r/s con 10 de burst.
Varias políticas en el mismo location
: aplica la más estricta
Se pueden encadenar varios limit_req
sobre la misma ruta. Si una política retrasa y otra también, prevalece la mayor demora; si una rechaza, el rechazo se impone aunque otra permita pasar.
Extensión del caso anterior para dar un “trato VIP” a las direcciones de confianza:
http {
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;
server {
location / {
limit_req zone=req_zone burst=10 nodelay; # resto
limit_req zone=req_zone_wl burst=20 nodelay; # allowlist
}
}
}
Lenguaje del código: PHP (php)
- Las redes de confianza no coinciden con
req_zone
(clave vacía) pero sí conreq_zone_wl
: quedan en 15 r/s. - El resto coincide con ambas, por lo que manda la más restrictiva: 5 r/s.
Registros, códigos de respuesta y otras piezas relacionadas
Cómo queda el registro cuando se frena o se rechaza
Por defecto, NGINX escribe una línea error
cuando rechaza y una de nivel inferior cuando demora. Ejemplo:
YYYY/MM/DD HH:MM:SS [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: ejemplo.com, request: "GET / HTTP/1.1", host: "ejemplo.com"
Lenguaje del código: PHP (php)
excess
indica cuánto se excede la tasa por milisegundo.- Para ajustar el nivel de registro, usar
limit_req_log_level
:
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_log_level warn; # en vez de error
proxy_pass http://my_upstream;
}
Lenguaje del código: PHP (php)
Elegir el código devuelto cuando se excede el límite
Por defecto se devuelve 503 (Service Temporarily Unavailable). Se puede cambiar con limit_req_status
:
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_status 444; # cierra sin respuesta formal
}
Lenguaje del código: PHP (php)
Bloquear por completo una ruta concreta
Si el objetivo es denegar todo en un endpoint determinado:
location /foo.php {
deny all;
}
Tamaño de la zona y memoria: evitar sustos
Recordatorio útil: 1 MiB ≈ 16.000 direcciones IP en la zona de limit_req
. Si la web recibe muchos visitantes únicos o ataques distribuidos, una zona pequeña puede quedarse sin hueco. Cuando NGINX necesita registrar una IP nueva:
- Purga entradas antiguas no usadas en 60 s (hasta dos por cada alta).
- Si aún no cabe, responde 503.
Pista de diagnóstico: si aparecen 503 sólo bajo mucho tráfico y el hit ratio de caché es alto, revise el tamaño de la zone
antes de culpar al backend.
Dónde aplicarlo para que sume y no reste
- Rutas sensibles:
/login/
,/reset-password/
,/wp-login.php
,/xmlrpc.php
, APIs de autenticación y endpoints con efectos de pago. - Rutas “calientes”: búsquedas, promociones, operaciones que “tiran” de base de datos.
- Agregadores y scrapers: en listados y feeds, el límite desacelera bots ansiosos sin afectar a usuarios reales.
Evite, en cambio, desplegar tasas estrictas sobre recursos estáticos o CDN de terceros —no tiene sentido “acelerar y frenar” bytes que puede servir cacheados—.
Errores habituales (y cómo evitarlos)
- Olvidar
burst
y castigar a usuarios reales: navegadores y apps móviles disparan ráfagas. Añadaburst
y, salvo necesidad contraria,nodelay
. - Tasas irreales: 5 r/s puede ser lógico para login; no tanto para una portada con 12 recursos. Ajuste
rate
,burst
y, si procede,delay
. - Zona minúscula: una zona de 1–2 MiB puede saturarse en sitios concurridos. Dimensione en función de la cardinalidad de IPs.
- Límites globales que tapan todo: aplique por ruta o por servidor; las políticas “a martillo” terminan penalizando a quien no debe.
- No registrar: sin logs, el ajuste es a ciegas. Active niveles coherentes con
limit_req_log_level
. - Confundir 429 con 503: el límite de NGINX devuelve 503 por defecto; si su capa de negocio espera 429 (Too Many Requests), conviene re-mapear el código o gestionarlo aguas arriba.
Propuesta de despliegue en tres pasos
- Medir
- Cuente recursos por página tipo (¿4–6? ¿hasta 12?).
- Revise logs de formularios y de APIs con más abuso.
- Determine cardinalidad de IPs (pico de únicas).
- Configurar
- Declare
limit_req_zone
enhttp
con tasa realista y zona suficiente. - Aplique
limit_req
en rutas sensibles conburst
+nodelay
. - Si su patrón de página es “varias descargas de golpe”, considere
delay
para un “pase libre” corto sin demoras y un “freno suave” después.
- Declare
- Observar y ajustar
- Vigile 503 asociados a la zona.
- Ajuste tasa, burst o zona según el patrón real.
- Añada excepciones con
geo
/map
para redes de confianza.
Ejemplo completo con rutas críticas y diferenciación por redes
http {
# 1) Clave condicional: allowlist sin límite, resto por IP
geo $is_trusted {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $is_trusted $rate_key {
0 "";
1 $binary_remote_addr;
}
# 2) Zonas: una general y otra más permisiva para trusted si se desea
limit_req_zone $rate_key zone=zone_login:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=zone_public:20m rate=10r/s;
server {
listen 80;
# /login: frenar fuerza bruta con ráfaga corta sin demora
location /login/ {
limit_req zone=zone_login burst=8 nodelay;
limit_req_log_level warn;
proxy_pass http://auth_upstream;
}
# Página principal y listados: ráfaga mayor, sin demorar
location / {
limit_req zone=zone_public burst=20 nodelay;
proxy_pass http://app_upstream;
}
# Bloquear por completo scripts que no deben ser públicos
location ~* \.php$ {
deny all;
}
}
}
Lenguaje del código: PHP (php)
Este esquema protege con 5 r/s el área de login (y 8 de ráfaga “sin freno”), permite 10 r/s en el resto con 20 de burst, y veta directamente la ejecución de PHP en el frontal. Las redes de confianza declaradas en geo
no sufren el límite de login al quedar con clave vacía.
Conclusión
La limitación de peticiones en NGINX no es un “truco” de última hora, sino una pieza estructural para preservar la salud de las aplicaciones. Entender tasa, ráfaga y demora —y, sobre todo, aplicarlos en el lugar correcto— permite absorber el comportamiento real de los clientes, frenar abusos y mantener a la aplicación trabajando en su “zona verde”. Si además se segmentan redes de confianza, se dimensiona bien la zona de memoria y se observan los logs, el resultado es una web más estable bajo carga y menos expuesta al ruido de fondo de Internet.
Preguntas frecuentes
¿Cómo elegir una tasa de peticiones por segundo adecuada para un formulario de inicio de sesión en NGINX?
Conviene partir de 4–6 recursos por página y del patrón de su login (¿incluye llamadas extra a APIs?). Un valor conservador para login suele estar en 5 r/s con burst
8–12 y nodelay
, ajustando tras observar registros. Si hay dispositivos antiguos o automatizaciones legítimas, amplíe ligeramente el burst.
¿Qué diferencia práctica hay entre burst
y nodelay
al limitar peticiones en NGINX?burst
define huecos de cola por encima de la tasa. Sin nodelay
, NGINX espacia la salida y puede añadir retardo visible. Con nodelay
, envía de inmediato mientras haya huecos y marca esos huecos como ocupados durante el intervalo correspondiente, manteniendo la tasa efectiva sin introducir latencia adicional.
¿Cuándo usar delay
para aplicar una limitación en dos etapas en NGINX?
Cuando una página típica descarga varias piezas a la vez (por ejemplo, hasta 12 recursos), delay
permite que las primeras X (por ejemplo, 8) pasen sin freno y, a partir de ahí, espacia para no superar la tasa (p. ej., 5 r/s). Si se rebasa el burst total, se rechaza hasta recuperar margen.
¿Cómo registrar y diagnosticar rechazos por exceso de peticiones en NGINX sin inundar los logs?
Use limit_req_log_level
para bajar el nivel (por ejemplo, a warn
) y filtre por zona en su stack de observabilidad. Vigile el campo excess
(exceso por milisegundo) y la métrica de 503 por zona. Si crecen sólo en picos, revise tamaño de la zona y burst
; si aparecen en horario valle, la tasa es probablemente demasiado baja o hay un cliente mal comportado que requiere reglas específicas.
vía: blog.nginx.org