La limitación de tasa es una de las primeras líneas de defensa más efectivas contra ataques DDoS, intentos de fuerza bruta en inicios de sesión y abuso de APIs. Nginx incluye dos poderosos módulos integrados — limit_req y limit_conn — que permiten controlar el tráfico entrante sin instalar software de terceros. En esta guía, configurarás ambos módulos con ejemplos listos para producción, excluirás IPs de confianza y probarás todo antes de implementarlo en vivo.
Requisitos Previos
- Nginx 1.18+ instalado en un servidor Linux (Ubuntu/Debian o RHEL/CentOS)
- Acceso root o sudo
- Familiaridad básica con bloques server de Nginx y sintaxis de configuración
- Una herramienta para pruebas de carga:
ab(Apache Bench),wrkocurl
Entendiendo la Limitación de Tasa en Nginx
El módulo limit_req de Nginx implementa el algoritmo de cubeta con fuga (leaky bucket). Imagina una cubeta con un pequeño orificio en el fondo: el agua (solicitudes) entra a tasas variables, pero drena a una tasa fija. Si la cubeta se desborda, el exceso de agua (solicitudes) es rechazado.
Dos conceptos clave impulsan la configuración:
- Zona: Un área de memoria compartida que rastrea los conteos de solicitudes por clave (generalmente la IP del cliente). Se define una vez en el bloque
httpy se referencia en bloquesserverolocation. - Tasa: El rendimiento permitido expresado como solicitudes por segundo (
r/s) o por minuto (r/m). Nginx internamente convierte todo a granularidad por segundo.
El módulo limit_conn funciona de manera diferente — limita el número de conexiones simultáneas abiertas desde una sola clave en lugar de la tasa de solicitudes.
Configurando la Limitación de Tasa de Solicitudes
Comienza definiendo zonas en el bloque http de /etc/nginx/nginx.conf:
http {
# General zone: 10 requests/second per IP, 10 MB shared memory
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# Strict zone for login endpoints: 1 request/second per IP
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Return 429 instead of the default 503
limit_req_status 429;
}
La variable $binary_remote_addr almacena las IPs de los clientes en un formato binario compacto — 10 MB pueden contener aproximadamente 160,000 direcciones.
Ahora aplica estas zonas en tu bloque server:
server {
listen 80;
server_name example.com;
# General rate limit for all requests
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://backend;
}
# Strict limit on login page
location /wp-login.php {
limit_req zone=login burst=3 nodelay;
proxy_pass http://backend;
}
# Custom 429 error page
error_page 429 /rate_limit.html;
location = /rate_limit.html {
internal;
return 429 '{"error": "Too many requests. Please retry after a few seconds."}';
default_type application/json;
}
}
El parámetro burst define cuántas solicitudes excedentes pueden encolarse antes de que Nginx comience a rechazar. Con nodelay, las solicitudes en ráfaga se procesan inmediatamente en lugar de espaciarse — esto proporciona una mejor experiencia de usuario para picos de tráfico legítimos.
Configurando Límites de Conexión
La limitación de conexiones evita que una sola IP mantenga demasiados sockets abiertos simultáneamente, que es el mecanismo central detrás de ataques estilo slowloris:
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn_status 429;
}
server {
# Max 10 simultaneous connections per IP
limit_conn addr 10;
# Limit bandwidth per connection (optional)
limit_rate 100k;
location /downloads/ {
limit_conn addr 2;
limit_rate 50k;
}
}
Comparación de Métodos de Limitación de Tasa
| Característica | limit_req | limit_conn | WAF Externo (Cloudflare/ModSecurity) |
|---|---|---|---|
| Controla | Tasa de solicitudes (req/s) | Conexiones concurrentes | Ambos + patrones de Capa 7 |
| Algoritmo | Cubeta con fuga | Contador de conexiones | Varía (ML, firmas) |
| Granularidad | Por clave (IP, header, etc.) | Por clave | Por regla, geo, ASN |
| Impacto en rendimiento | Mínimo | Mínimo | Ligera latencia añadida |
| Efectividad contra DDoS | Buena para inundaciones L7 | Buena para slowloris | Mejor para volumétricos |
| Configuración | Solo config de Nginx | Solo config de Nginx | Servicio/agente separado |
| Costo | Gratis | Gratis | Nivel gratuito o de pago |
Para la mayoría de los servidores, combinar limit_req + limit_conn localmente con un WAF externo proporciona defensa en profundidad.
Escenario del Mundo Real
Tienes un servidor de producción ejecutando WordPress detrás de Nginx. Tus logs de acceso muestran miles de solicitudes POST golpeando /wp-login.php y /xmlrpc.php desde IPs rotativas — un ataque de fuerza bruta distribuido. El uso de CPU está disparándose y los usuarios legítimos ven tiempos de espera agotados.
Aquí tienes una configuración de producción que aborda este problema:
http {
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=1r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
limit_req_status 429;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
listen 443 ssl;
server_name example.com;
limit_conn conn_limit 15;
location / {
limit_req zone=general burst=50 nodelay;
proxy_pass http://127.0.0.1:8080;
}
location = /wp-login.php {
limit_req zone=wp_login burst=3 nodelay;
proxy_pass http://127.0.0.1:8080;
}
# Block xmlrpc.php almost entirely — 1 request per minute
location = /xmlrpc.php {
limit_req zone=xmlrpc burst=1 nodelay;
proxy_pass http://127.0.0.1:8080;
}
}
Después de aplicar esta configuración y ejecutar nginx -t && systemctl reload nginx, el tráfico de fuerza bruta recibe respuestas 429 mientras los usuarios legítimos continúan autenticándose normalmente.
Exclusión de IPs de Confianza
Los sistemas de monitoreo, verificaciones de salud y redes de oficina deben evadir los límites de tasa. Usa el módulo geo para crear una lista blanca:
http {
geo $rate_limit_key {
default $binary_remote_addr;
10.0.0.0/8 ""; # Internal network
192.168.1.0/24 ""; # Office network
203.0.113.50 ""; # Monitoring server
}
# Empty key = no tracking = no rate limit
limit_req_zone $rate_limit_key zone=general:10m rate=10r/s;
}
Cuando $rate_limit_key se resuelve a una cadena vacía, Nginx omite completamente el rastreo de limitación de tasa para esa solicitud.
Pruebas de Limitación de Tasa
Siempre prueba en un entorno de staging antes de implementar en producción. Usa limit_req_dry_run on para registrar violaciones sin bloquear:
location / {
limit_req zone=general burst=20 nodelay;
limit_req_dry_run on; # Log only, don't block
}
Luego genera tráfico con Apache Bench:
# Send 100 requests with 10 concurrent connections
ab -n 100 -c 10 https://staging.example.com/
# Check how many were rate limited
grep "limiting requests" /var/log/nginx/error.log | wc -l
# Check 429 responses in access log
awk '$9 == 429' /var/log/nginx/access.log | wc -l
O usa wrk para pruebas de carga sostenida:
wrk -t4 -c50 -d30s https://staging.example.com/
Una vez que estés satisfecho con los umbrales, elimina la directiva limit_req_dry_run y recarga Nginx.
Trampas y Casos Especiales
- Clientes detrás de CDN/proxy: Si Nginx está detrás de Cloudflare, un balanceador de carga o un proxy inverso,
$binary_remote_addrserá la IP del proxy — no la del cliente. Usaset_real_ip_fromyreal_ip_headerpara extraer la IP real del cliente:
set_real_ip_from 173.245.48.0/20; # Cloudflare IP range
set_real_ip_from 103.21.244.0/22;
real_ip_header CF-Connecting-IP;
- IPs compartidas (NAT/corporativas): Límites de tasa agresivos pueden bloquear oficinas enteras detrás de una sola IP NAT. Establece valores de burst razonables y excluye los rangos corporativos conocidos.
- Agotamiento de memoria de zona: Si la zona se queda sin memoria, Nginx devuelve 503 para todas las nuevas claves. Monitorea el uso de la zona y dimensiona adecuadamente — 1 MB contiene aproximadamente 16,000 direcciones IP.
- Malentendido de tasa vs ráfaga: Una tasa de
10r/sconburst=20no permite 30 solicitudes/segundo. Permite una cola de ráfaga de 20 solicitudes que drenan a 10/s. Sinnodelay, las solicitudes encoladas experimentan un retraso artificial. - Ruido en los logs: La limitación de tasa con alto tráfico llena los logs de error rápidamente. Usa un
error_logseparado para las ubicaciones con limitación de tasa o ajusta los niveles de log.
Resumen
- Usa
limit_reqpara controlar las tasas de solicitudes con el algoritmo de cubeta con fuga — ideal para páginas de inicio de sesión, APIs y protección general contra inundaciones. - Usa
limit_connpara limitar conexiones concurrentes por IP — efectivo contra ataques slowloris y de agotamiento de recursos. - Siempre cambia el código de estado predeterminado a 429 con
limit_req_status 429. - Excluye IPs internas usando el módulo
geopara que el monitoreo y las verificaciones de salud nunca sean bloqueados. - Prueba con
limit_req_dry_run ony herramientas comoabowrkantes de aplicar en producción. - Contempla proxies y CDNs configurando
real_ip_headerpara identificar la IP real del cliente. - Combina la limitación de tasa de Nginx con Fail2ban y un WAF externo para defensa en profundidad.