El rate limiting limita cuántas peticiones puede hacer un mismo cliente en un intervalo de tiempo. Sirve para frenar ataques de fuerza bruta contra formularios de login, scrapers agresivos y clientes de API que se saltan sus cuotas, sin afectar a los usuarios normales. En este tutorial configurarás en Ubuntu 24.04 los módulos limit_req y limit_conn de Nginx con límites distintos para el sitio, el login y la API, y comprobarás que funcionan.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Nginx instalado y sirviendo un sitio, con su configuración en
/etc/nginx/sites-available/your_domain.
Los módulos limit_req y limit_conn forman parte de Nginx y vienen incluidos en el paquete de Ubuntu; no hay que instalar nada.
Cómo funciona limit_req
Nginx aplica el rate limiting con dos directivas:
limit_req_zone, en el bloquehttp, define una zona de memoria compartida: la clave que identifica a cada cliente (normalmente su IP), el tamaño de la zona y la tasa permitida.limit_req, en unserverolocation, aplica esa zona a las peticiones que llegan ahí.
El algoritmo es un "leaky bucket": con rate=10r/s, Nginx acepta como máximo una petición cada 100 ms por cliente. Las peticiones que llegan más rápido se rechazan, salvo que definas un margen de ráfaga (burst). Por eso casi siempre se combina la tasa con burst: un navegador que carga una página pide HTML, CSS, JavaScript e imágenes casi a la vez.
Sobre el tamaño de la zona: con $binary_remote_addr como clave, cada estado ocupa 128 bytes en sistemas de 64 bits, así que una zona de 10 MB guarda unas 80.000 direcciones IP. Si la zona se llena, Nginx elimina los estados más antiguos.
Paso 1: Definir las zonas de límite
Crea un archivo en /etc/nginx/conf.d/, que Nginx incluye dentro del bloque http:
sudo nano /etc/nginx/conf.d/rate-limit.conf
# Límite general del sitio: 10 peticiones por segundo por IP
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# Login: 5 peticiones por minuto por IP
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# API: 30 peticiones por segundo por IP
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
# Conexiones simultáneas por IP
limit_conn_zone $binary_remote_addr zone=perip_conn:10m;
# Responder 429 Too Many Requests en lugar del 503 por defecto
limit_req_status 429;
limit_conn_status 429;
$binary_remote_addr es la IP del cliente en formato binario (4 bytes para IPv4, 16 para IPv6), más compacta que $remote_addr. El código 429 es el estándar para "demasiadas peticiones" y permite a los clientes distinguirlo de una caída real del servicio.
Paso 2: Aplicar los límites al sitio
Edita el bloque server de tu sitio:
sudo nano /etc/nginx/sites-available/your_domain
Añade los límites dentro del server. Adapta las rutas /login y /api/ a las de tu aplicación:
server {
# ... listen, server_name, root, etc.
# Límite general para todo el sitio
limit_req zone=general burst=20 nodelay;
limit_conn perip_conn 20;
location = /login {
limit_req zone=login burst=3 nodelay;
# ... el mismo proxy_pass, fastcgi_pass o try_files que usa el resto del sitio
}
location /api/ {
limit_req zone=api burst=50 nodelay;
# ... proxy_pass http://127.0.0.1:3000; o equivalente
}
location / {
try_files $uri $uri/ =404;
}
}
Qué hace cada parámetro:
burst=20permite que un cliente supere la tasa con hasta 20 peticiones extra, que se contabilizan en una cola.nodelayatiende las peticiones de la ráfaga inmediatamente en lugar de espaciarlas al ritmo de la tasa. Sinnodelay, Nginx las retrasa y el usuario percibe la web lenta. Con él, la ráfaga pasa sin espera y el hueco se va liberando a la velocidad derate.limit_conn perip_conn 20limita a 20 las conexiones abiertas a la vez desde una misma IP.
Un limit_req definido en una location sustituye al del nivel server para esa location: el login solo aplica la zona login y la API solo la zona api. Si quieres que en una location se apliquen ambas, escribe las dos directivas limit_req dentro de ella.
Importanteen el login, si la ruta la gestiona PHP u otra aplicación, la
location = /logindebe incluir la misma directiva de paso al backend que el resto del sitio (fastcgi_pass,proxy_pass...). Unalocationsin ella devolvería 404 o serviría el archivo tal cual.
Paso 3: Probar la configuración en modo simulación
Antes de bloquear tráfico real, puedes activar el modo de prueba. Con limit_req_dry_run on, Nginx calcula los límites y registra en el log de errores qué peticiones habría rechazado, pero las deja pasar. Añádelo en /etc/nginx/conf.d/rate-limit.conf:
limit_req_dry_run on;
Comprueba la sintaxis y recarga:
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Deja el modo simulación unas horas con tráfico real y revisa cuántas peticiones legítimas se habrían bloqueado:
sudo grep "limiting requests, dry run" /var/log/nginx/error.log | tail -5
2026/09/25 11:02:13 [error] 1234#1234: *5871 limiting requests, dry run, excess: 20.550 by zone "general", client: 203.0.113.25, server: your_domain, request: "GET /img/banner.png HTTP/1.1", host: "your_domain"
Si aparecen muchas IP de usuarios normales, sube rate o burst en esa zona. Cuando estés satisfecho, elimina la línea limit_req_dry_run on; y recarga Nginx.
Paso 4: Verificar que el límite se aplica
Lanza 10 peticiones seguidas contra el login desde el propio servidor y cuenta los códigos de estado. Con rate=5r/m y burst=3, pasan la primera y las 3 de la ráfaga, y el resto recibe 429:
for i in $(seq 1 10); do curl -s -o /dev/null -w "%{http_code}\n" -H "Host: your_domain" http://127.0.0.1/login; done | sort | uniq -c
4 200
6 429
Si tu login responde con otro código (302, 405...), verás ese código en lugar de 200; lo importante es que aparezcan los 429. Para el límite general, genera más carga con Apache Bench, incluido en el paquete apache2-utils:
sudo apt install apache2-utils
ab -n 200 -c 20 -H "Host: your_domain" http://127.0.0.1/
En el resumen, la línea Non-2xx responses indica cuántas peticiones se han rechazado. Nginx registra cada rechazo en el log de errores:
sudo grep "limiting requests" /var/log/nginx/error.log | tail -3
Si el sitio fuerza HTTPS y responde con una redirección, usa https://your_domain/ en lugar de 127.0.0.1. Si lanzas las pruebas desde tu equipo, tu IP quedará limitada unos segundos, lo cual es normal.
Paso 5: Excluir IP de confianza
Tus sistemas de monitorización, tu oficina o otros servidores propios no deberían sufrir el límite. Nginx no cuenta las peticiones cuya clave está vacía, así que puedes usar geo y map para dejar la clave vacía en esas IP. Edita /etc/nginx/conf.d/rate-limit.conf:
sudo nano /etc/nginx/conf.d/rate-limit.conf
Añade al principio del archivo:
geo $rate_limit_exempt {
default 0;
203.0.113.10 1; # monitorización
198.51.100.0/24 1; # red de la oficina
}
map $rate_limit_exempt $rate_limit_key {
0 $binary_remote_addr;
1 "";
}
Y cambia la clave de las zonas de $binary_remote_addr a $rate_limit_key:
limit_req_zone $rate_limit_key zone=general:10m rate=10r/s;
limit_req_zone $rate_limit_key zone=login:10m rate=5r/m;
limit_req_zone $rate_limit_key zone=api:10m rate=30r/s;
limit_conn_zone $rate_limit_key zone=perip_conn:10m;
Recarga Nginx:
sudo nginx -t && sudo systemctl reload nginx
Para comprobar la exclusión, añade temporalmente 127.0.0.1 1; al bloque geo, recarga y repite la prueba del paso 4: ya no verás respuestas 429. Después quita esa línea y recarga de nuevo, porque el resto de pruebas de esta guía se hacen desde el propio servidor.
Paso 6: Usar la IP real detrás de un proxy o CDN
Si el servidor está detrás de un balanceador de carga, Cloudflare u otra CDN, $binary_remote_addr es la IP del proxy y todos tus visitantes compartirían el mismo límite. El módulo realip, incluido en Nginx, sustituye la dirección del cliente por la que viene en una cabecera, pero solo si la conexión llega desde un proxy de confianza. Crea el archivo:
sudo nano /etc/nginx/conf.d/real-ip.conf
# Rangos del proxy o balanceador (sustitúyelos por los reales)
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Con Cloudflare, añade sus rangos publicados en https://www.cloudflare.com/ips/ y usa real_ip_header CF-Connecting-IP;. No confíes en X-Forwarded-For sin set_real_ip_from: cualquier cliente podría enviar una IP falsa en esa cabecera y saltarse el límite.
Recarga y comprueba en el log de acceso que aparecen las IP de los visitantes en lugar de la del proxy:
sudo nginx -t && sudo systemctl reload nginx
sudo tail -n 3 /var/log/nginx/access.log
Paso 7: Devolver una respuesta útil al cliente
Por defecto, un 429 devuelve una página HTML genérica. Para una API es mejor responder JSON con una cabecera Retry-After. Dentro del server de tu sitio:
error_page 429 = @too_many_requests;
location @too_many_requests {
default_type application/json;
add_header Retry-After 60 always;
return 429 '{"detail":"Too many requests, try again later"}';
}
Recarga Nginx y repite la prueba del login con curl -i para ver la respuesta completa:
curl -i -H "Host: your_domain" http://127.0.0.1/login
HTTP/1.1 429 Too Many Requests
Server: nginx
Content-Type: application/json
Retry-After: 60
{"detail":"Too many requests, try again later"}
Solución de problemas
El límite no se aplica. Comprueba que Nginx ha cargado tu archivo con sudo nginx -T | grep limit_req. Revisa también que la petición entra en la location que tiene el limit_req y que tu IP no está en la lista de exentas del paso 5.
Usuarios legítimos reciben 429. Suele ocurrir con páginas que cargan muchos recursos o con varios usuarios detrás de la misma IP (una oficina, una red móvil con CGNAT). Sube burst, aplica el límite solo a las rutas dinámicas y deja fuera los archivos estáticos con una location propia sin limit_req.
Todos los usuarios comparten el límite. Estás detrás de un proxy y Nginx ve la IP del proxy. Configura el paso 6.
Error "zero size shared memory zone". Una directiva limit_req usa una zona que no está definida con limit_req_zone, o el nombre está mal escrito. Los nombres de zona deben coincidir exactamente.
Conclusión
Nginx limita ahora las peticiones por IP con tres niveles (sitio, login y API), permite ráfagas razonables, excluye tus IP de confianza, identifica bien a los clientes detrás de un proxy y devuelve un 429 claro. Revisa el log de errores las primeras semanas para ajustar las tasas al tráfico real.
Como siguientes pasos puedes:
- Configurar una jail de Fail2ban que lea las líneas
limiting requestsdel log de errores y bloquee en el firewall las IP reincidentes. - Añadir el tiempo de respuesta a los logs de acceso para ver si el rate limiting reduce la carga del backend.
- Revisar el resto de la configuración de seguridad de Nginx: ocultar la versión, cabeceras de seguridad y TLS moderno.
