--- title: "Cómo configurar rate limiting en Nginx en Ubuntu 24.04" description: "Limita las peticiones por IP en Nginx con limit_req y limit_conn en Ubuntu 24.04: protege login y API, gestiona ráfagas, excluye IP de confianza y pruébalo." category: "Servidores Web" language: es source: https://cubepath.com/es/docs/servidores-web/configuracion-de-rate-limiting-en-nginx updated: 2026-09-25 --- # Cómo configurar rate limiting en Nginx en Ubuntu 24.04 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 bloque `http`, 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 un `server` o `location`, 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`: ```bash sudo nano /etc/nginx/conf.d/rate-limit.conf ``` ```nginx # 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: ```bash 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: ```nginx 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=20` permite que un cliente supere la tasa con hasta 20 peticiones extra, que se contabilizan en una cola. - `nodelay` atiende las peticiones de la ráfaga inmediatamente en lugar de espaciarlas al ritmo de la tasa. Sin `nodelay`, 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 de `rate`. - `limit_conn perip_conn 20` limita 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. > Importante: en el login, si la ruta la gestiona PHP u otra aplicación, la `location = /login` debe incluir la misma directiva de paso al backend que el resto del sitio (`fastcgi_pass`, `proxy_pass`...). Una `location` sin 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`: ```nginx limit_req_dry_run on; ``` Comprueba la sintaxis y recarga: ```bash sudo nginx -t sudo systemctl reload nginx ``` ```output 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: ```bash sudo grep "limiting requests, dry run" /var/log/nginx/error.log | tail -5 ``` ```output 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: ```bash 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 ``` ```output 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`: ```bash 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: ```bash 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`: ```bash sudo nano /etc/nginx/conf.d/rate-limit.conf ``` Añade al principio del archivo: ```nginx 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`: ```nginx 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: ```bash 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: ```bash sudo nano /etc/nginx/conf.d/real-ip.conf ``` ```nginx # 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: ```bash 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: ```nginx 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: ```bash curl -i -H "Host: your_domain" http://127.0.0.1/login ``` ```output 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 requests` del 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.