--- title: "Rate limiting de APIs con Nginx y HAProxy en Ubuntu 24.04" description: "Protege tu API limitando peticiones por IP y por clave de API con Nginx y HAProxy en Ubuntu 24.04, con respuestas 429, listas blancas y pruebas de carga." category: "Proxy y Equilibrio de Carga" language: es source: https://cubepath.com/es/docs/proxy-y-equilibrio-de-carga/estrategias-de-limitacion-de-velocidad-para-api updated: 2026-09-25 --- # Rate limiting de APIs con Nginx y HAProxy en Ubuntu 24.04 La limitación de peticiones (rate limiting) controla cuántas solicitudes puede hacer cada cliente a tu API en un intervalo de tiempo. Sirve para frenar abusos y fuerza bruta, repartir la capacidad entre clientes y evitar que un pico de tráfico tumbe el backend. En este tutorial configurarás Nginx en Ubuntu 24.04 como proxy inverso delante de una API con límites por IP, por clave de API y por endpoint, devolviendo `429 Too Many Requests` en JSON. Al final verás cómo hacer lo mismo con las stick tables de HAProxy. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - Una API escuchando en local, en este tutorial en `127.0.0.1:8080`. Si todavía no tienes una, el paso 1 levanta un backend de prueba. - Un nombre de dominio apuntando al servidor (en los ejemplos, `your_domain`) o, para pruebas, la IP del servidor. - El puerto 80 abierto en el firewall. ## Cómo funcionan los algoritmos de limitación Antes de configurar nada conviene saber qué algoritmo usa cada herramienta, porque determina cómo se comportan las ráfagas: | Algoritmo | Idea | Dónde lo encuentras | |---|---|---| | Leaky bucket | Las peticiones salen a ritmo constante; las que desbordan la cola se rechazan | Nginx (`limit_req`) | | Token bucket | Se acumulan fichas a ritmo fijo; cada petición gasta una y permite ráfagas hasta el tamaño del cubo | Muchas librerías y API gateways | | Ventana fija | Contador por intervalo (por ejemplo, 100 por minuto) que se reinicia al cambiar de minuto | Implementaciones simples con Redis `INCR` + `EXPIRE` | | Ventana deslizante | Tasa calculada sobre los últimos N segundos, sin saltos al cambiar de intervalo | HAProxy (`http_req_rate`) | En Nginx, `rate` fija el ritmo del cubo y `burst` el tamaño de la cola. En HAProxy defines una ventana (por ejemplo, 10 segundos) y comparas la tasa medida con un umbral. ## Paso 1: Instalar Nginx y preparar un backend de prueba Instala Nginx desde los repositorios de Ubuntu: ```bash sudo apt update sudo apt install nginx ``` Comprueba que el servicio está activo: ```bash systemctl status nginx --no-pager ``` ```output ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) ``` Si ya tienes tu API en `127.0.0.1:8080`, salta al paso 2. Si no, abre una segunda sesión SSH y levanta un servidor HTTP mínimo que haga de backend mientras sigues el tutorial: ```bash mkdir -p ~/api-test/api && echo '{"status":"ok"}' > ~/api-test/api/index.html cd ~/api-test && python3 -m http.server 8080 --bind 127.0.0.1 ``` > Importante: el límite de Nginx se aplica en la fase de acceso de la petición. Una directiva `return 200` se ejecuta antes, así que una location que solo hace `return` nunca se limitará. Prueba siempre contra un `proxy_pass` real. ## Paso 2: Definir las zonas de limitación Las zonas son memoria compartida donde Nginx guarda el estado de cada clave (IP, clave de API...). Se declaran en el contexto `http`. En Ubuntu, todo lo que pongas en `/etc/nginx/conf.d/` se incluye dentro de ese bloque, así que crea un archivo dedicado: ```bash sudo nano /etc/nginx/conf.d/ratelimit.conf ``` ```nginx # IPs que no se limitan (monitorización, oficina, otros servidores propios) geo $rl_exempt { default 0; 127.0.0.1 1; 10.0.0.0/8 1; } # Clave de limitación por IP: vacía para las IPs exentas. # Nginx no contabiliza las peticiones cuya clave está vacía. map $rl_exempt $rl_ip_key { 0 $binary_remote_addr; 1 ""; } # 10 peticiones por segundo por IP limit_req_zone $rl_ip_key zone=api_ip:10m rate=10r/s; # 5 peticiones por minuto por IP para el login limit_req_zone $rl_ip_key zone=api_login:10m rate=5r/m; # 600 peticiones por minuto por clave de API (cabecera X-API-Key) limit_req_zone $http_x_api_key zone=api_key:10m rate=600r/m; # Máximo de conexiones simultáneas por IP limit_conn_zone $rl_ip_key zone=api_conn:10m; # Código de respuesta cuando se supera un límite limit_req_status 429; limit_conn_status 429; limit_req_log_level warn; ``` Detalles importantes: - `$binary_remote_addr` ocupa 4 bytes por IPv4 y 16 por IPv6, frente a los hasta 39 de `$remote_addr`. Una zona de 10 MB guarda unas 160.000 claves. - La zona `api_key` usa la cabecera `X-API-Key` tal cual. Las peticiones sin cabecera tienen la clave vacía y no cuentan en esa zona, pero sí en la de IP. - Sustituye `10.0.0.0/8` por las redes que de verdad quieras excluir, o elimina la línea. > Nota: si Nginx está detrás de otro proxy o balanceador, `$binary_remote_addr` será la IP del balanceador y todos los clientes compartirán el mismo límite. En ese caso configura el módulo `real_ip` (`set_real_ip_from` y `real_ip_header`) antes de limitar. ## Paso 3: Aplicar los límites en el virtual host Crea el virtual host de la API: ```bash sudo nano /etc/nginx/sites-available/api ``` ```nginx upstream api_backend { server 127.0.0.1:8080; keepalive 32; } server { listen 80; server_name your_domain; # Respuesta JSON para las peticiones limitadas error_page 429 = @ratelimited; location @ratelimited { default_type application/json; add_header Retry-After 1 always; return 429 '{"detail":"Too many requests"}\n'; } # Límite general de la API location /api/ { limit_req zone=api_ip burst=20 nodelay; limit_req zone=api_key burst=50 nodelay; limit_conn api_conn 20; proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Endpoint sensible: límite mucho más estricto location = /api/login { limit_req zone=api_login burst=3 nodelay; proxy_pass http://api_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` Qué hace cada parámetro: - `burst=20` permite que un cliente supere el ritmo de 10 r/s con hasta 20 peticiones extra en cola. Por encima de eso, `429`. - `nodelay` atiende las peticiones de la ráfaga inmediatamente en vez de espaciarlas; el hueco se libera al ritmo definido. Sin `nodelay`, Nginx retrasa las peticiones para ajustarlas al ritmo, lo que aumenta la latencia percibida. - Cuando hay varias directivas `limit_req` en la misma location, se aplican todas: una petición con clave de API debe respetar el límite de su IP y el de su clave. - `location = /api/login` es una coincidencia exacta y gana a `/api/`, así que el login solo tiene su propio límite estricto. Activa el sitio, desactiva el sitio por defecto, valida la sintaxis y recarga: ```bash sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/api sudo rm /etc/nginx/sites-enabled/default 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 ``` > Consejo: antes de activar límites en producción puedes añadir `limit_req_dry_run on;` en la location. Nginx registra qué peticiones habría rechazado sin rechazarlas, así ajustas `rate` y `burst` con tráfico real. ## Paso 4: Probar los límites Lanza 40 peticiones seguidas contra la API y cuenta los códigos de respuesta: ```bash for i in $(seq 1 40); do curl -s -o /dev/null -w '%{http_code}\n' http://your_domain/api/; done | sort | uniq -c ``` Con `rate=10r/s` y `burst=20`, las primeras 21 aproximadamente pasan y el resto se rechazan: ```output 21 200 19 429 ``` Comprueba el cuerpo y las cabeceras de una respuesta limitada repitiendo el bucle y consultando una vez más: ```bash curl -si http://your_domain/api/ ``` ```output HTTP/1.1 429 Too Many Requests Server: nginx/1.24.0 (Ubuntu) Content-Type: application/json Retry-After: 1 {"detail":"Too many requests"} ``` Prueba el endpoint de login, que admite solo 4 peticiones seguidas (1 más la ráfaga de 3): ```bash for i in $(seq 1 8); do curl -s -o /dev/null -w '%{http_code}\n' -X POST http://your_domain/api/login; done ``` Las cuatro primeras llegan al backend (con el servidor de prueba de Python verás `501`, porque no implementa POST) y las cuatro siguientes reciben `429`. Por último, confirma que Nginx registra los rechazos en el log de errores: ```bash sudo tail -n 3 /var/log/nginx/error.log ``` ```output 2026/09/25 10:12:41 [warn] 1234#1234: *57 limiting requests, excess: 20.410 by zone "api_ip", client: 203.0.113.10, server: your_domain, request: "GET /api/ HTTP/1.1", host: "your_domain" ``` ## Paso 5: Registrar el estado del límite en el log de acceso La variable `$limit_req_status` indica si cada petición pasó (`PASSED`), se retrasó (`DELAYED`), se rechazó (`REJECTED`) o se habría rechazado en modo de prueba (`REJECTED_DRY_RUN`). Añádela a un formato de log propio en `/etc/nginx/conf.d/ratelimit.conf`: ```nginx log_format ratelimit '$remote_addr [$time_local] "$request" $status ' 'key="$http_x_api_key" limit=$limit_req_status'; ``` Y úsalo dentro del bloque `server` del virtual host: ```nginx access_log /var/log/nginx/api_access.log ratelimit; ``` Recarga y consulta qué IPs reciben más rechazos: ```bash sudo nginx -t && sudo systemctl reload nginx sudo grep 'limit=REJECTED' /var/log/nginx/api_access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head ``` > Advertencia: si tus clientes envían la clave de API en `X-API-Key`, este formato la escribe en el log en claro. Si eso no es aceptable, quita el campo `key=` del `log_format`. ## Alternativa: limitación con HAProxy HAProxy mide la tasa de peticiones con stick tables, que calculan una ventana deslizante por clave. Instálalo con: ```bash sudo apt install haproxy ``` Edita la configuración y añade un frontend y un backend al final del archivo (si Nginx sigue escuchando en el puerto 80, detenlo o cambia el `bind` para esta prueba): ```bash sudo nano /etc/haproxy/haproxy.cfg ``` ```haproxy frontend api bind :80 mode http # Tasa por IP en ventana de 10 segundos stick-table type ip size 100k expire 30s store http_req_rate(10s) http-request track-sc0 src # Tasa por clave de API, en una tabla separada del backend auxiliar http-request track-sc1 req.hdr(X-API-Key) table api_keys if { req.hdr(X-API-Key) -m found } # Límites: 100 peticiones cada 10 s por IP, 500 cada 10 s por clave http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 } http-request deny deny_status 429 if { sc_http_req_rate(1) gt 500 } default_backend api_servers backend api_keys stick-table type string len 64 size 100k expire 60s store http_req_rate(10s) backend api_servers mode http server api1 127.0.0.1:8080 check ``` Valida y recarga: ```bash sudo haproxy -c -f /etc/haproxy/haproxy.cfg sudo systemctl reload haproxy ``` Puedes ver en directo el contenido de la tabla por IP a través del socket de administración, que Ubuntu habilita por defecto en `/run/haproxy/admin.sock`: ```bash echo "show table api" | sudo socat stdio /run/haproxy/admin.sock ``` ```output # table: api, type: ip, size:102400, used:1 0x55d0c1a2b3c0: key=203.0.113.10 use=0 exp=28512 shard=0 http_req_rate(10000)=37 ``` Si `socat` no está instalado, instálalo con `sudo apt install socat`. ## Limitación en varios servidores Las zonas de Nginx y las stick tables de HAProxy viven en la memoria de cada servidor. Con tres balanceadores detrás de un DNS round robin, un cliente puede hacer hasta tres veces el límite. Hay tres soluciones habituales: - Dividir el límite entre el número de nodos (sencillo e impreciso). - En HAProxy, sincronizar las stick tables entre nodos con una sección `peers`. - Aplicar el límite en la propia API con un contador compartido en Redis (`INCR` con `EXPIRE` para ventana fija, o un sorted set para ventana deslizante) cuando necesites límites exactos por usuario o por plan. Una estrategia combinada suele funcionar mejor: límites generosos por IP en el proxy para cortar floods, y límites de negocio por usuario o plan en la aplicación. ## Solución de problemas - **Todos los clientes comparten el límite**: Nginx ve la IP del balanceador o de Cloudflare. Configura `set_real_ip_from` con los rangos del proxy y `real_ip_header X-Forwarded-For` (o `CF-Connecting-IP`). - **Devuelve 503 en vez de 429**: falta `limit_req_status 429;`. El valor por defecto es 503. - **`zero size shared memory zone`** al validar: una directiva `limit_req` usa una zona que no se ha declarado con `limit_req_zone`, o hay un error tipográfico en el nombre. - **El límite no se aplica nunca**: comprueba que la petición cae en la location correcta y que esa location hace `proxy_pass` y no un `return`. ## Conclusión Has configurado límites por IP, por clave de API y por endpoint en Nginx, con respuestas `429` en JSON, una lista blanca de redes y registro del estado de cada petición, y has visto el equivalente con stick tables en HAProxy. Como siguientes pasos, activa HTTPS con Let's Encrypt en el virtual host, ajusta `rate` y `burst` con `limit_req_dry_run` sobre tráfico real y documenta los límites para los clientes de tu API.