Limitación de tasa y WAF frente a contenedores Docker sin bloquear a usuarios reales

¿Te fue útil?

Todo funciona en staging. Luego producción es descubierta—por clientes, por scrapers, por “investigadores de seguridad” y por lo que sea que viva ese día dentro del espacio de direcciones IP públicas. Añades un WAF y un rate limit. Las alertas se calman. Y entonces la cola de soporte se enciende: “No puedo iniciar sesión”, “El pago falla”, “La API dice 429”, “La IP de mi oficina está bloqueada”, “Su sitio odia hoteles”.

Esto es lo que nadie vende en la demo del producto: la limitación de tasa y los WAFs no son barandillas de seguridad, son volantes. Si no diriges—si no observas, ajustas y modelas el comportamiento real de usuarios—vas a bloquear por completo a quienes pagan tus facturas, mientras los bots rotan IPs sin inmutarse y siguen adelante.

Un modelo mental práctico: WAF vs limitación de tasa vs “solo un reverse proxy”

Pon los palabreos a un lado por un minuto y piensa en términos de modos de fallo.

Limitación de tasa

La limitación de tasa trata de la protección de capacidad y del coste del abuso. Responde: “¿Cuántas solicitudes permitimos desde un solo actor en una ventana?” No responde a: “¿Es esta solicitud maliciosa?” Tampoco detiene automáticamente el abuso distribuido (una botnet puede ser educada por IP mientras te derrite en conjunto).

WAF (web application firewall)

Un WAF trata del riesgo de entrada. Responde: “¿Parece esta solicitud un intento de exploit o abuso de protocolo?” Es coincidencia de patrones y detección de anomalías—a veces útil, a veces hilarantemente errónea. Los WAFs reducen el ruido de ataques comunes (barridos de inyección SQL, sondas de path traversal), pero también castigan tráfico inusual pero legítimo, como APIs con cadenas de consulta raras o usuarios detrás de proxies empresariales.

Reverse proxy

El reverse proxy es tu oficial de tráfico: terminación TLS, enrutamiento, normalización de cabeceras, pooling de conexiones y el lugar donde puedes implementar tanto filtrado tipo WAF como limitación de tasa. Si ejecutas Docker, un reverse proxy no es opcional a menos que disfrutes exponer cada puerto de contenedor a internet como si fuera 2014.

Opinión: Coloca la lógica de rate limiting y WAF en el borde que puedas observar y versionar. Eso significa un proxy que controles (Nginx/HAProxy/Traefik) o un edge gestionado (CDN/proveedor WAF) donde todavía obtengas logs y puedas probar cambios. La “seguridad en caja negra” es cómo terminas depurando producción con vibras raras.

Una cita para mantener pegada en tu monitor: “La esperanza no es una estrategia.” — atribuida a varios operadores; trátala como una idea parafraseada, no como escritura sagrada.

Hechos y contexto histórico (lo que explica el dolor actual)

  • Los WAFs se volvieron mainstream tras los exploits web de principios de los 2000: OWASP empezó en 2001; el primer OWASP Top 10 apareció en 2003 y moldeó conjuntos de reglas WAF durante dos décadas.
  • ModSecurity comenzó como un módulo de Apache (2002): popularizó el modelo de “motor de reglas”, que luego apareció en Nginx, controladores ingress y WAFs gestionados.
  • La limitación de tasa es anterior a la web: los algoritmos token bucket y leaky bucket vienen del trabajo de telecomunicaciones/redes de los años 80; siguen siendo los primitivos centrales hoy.
  • keep-alive de HTTP/1.1 cambió las matemáticas: una vez que los clientes pueden reutilizar conexiones, los límites por conexión se volvieron inútiles, y los controles por solicitud/identidad importaron más.
  • El NAT se metió por todas partes: operadoras móviles y redes corporativas cada vez más ponen miles de usuarios detrás de unas pocas IPs de salida, así que los límites “por IP” empezaron a castigar a multitudes inocentes.
  • Los CDNs cambiaron el problema de la “IP cliente real”: cuando el tráfico termina en un CDN/WAF, tu origen ve la IP del CDN a menos que confíes y parsees explícitamente las cabeceras forwarded.
  • HTTP/2 multiplexing permite altas tasas de solicitudes por conexión: si solo limitas conexiones, un único cliente aún puede enviar un aluvión de streams.
  • Los contenedores hicieron que el “edge” sea un objetivo móvil: en la era de las VM, el balanceador era estable; en tierra de contenedores, los equipos siguen intentando meter seguridad en el contenedor de la app. Eso es adorable. No lo hagas.

Broma #1: Un conjunto de reglas WAF es como una planta de interior—la ignoras un mes y morirá, y posiblemente se llevará tu fin de semana con ella.

Patrones de arquitectura recomendados (y cuándo elegir cada uno)

Patrón A: CDN/proveedor WAF → reverse proxy → servicios Docker

Este es el montaje de “supervisión adulta”. Tu edge gestionado absorbe la basura volumétrica y maneja mitigación básica de bots. Tu reverse proxy hace enforcements de enrutamiento preciso, límites conscientes de la app y emite logs que puedes correlacionar.

Úsalo cuando: estés expuesto a internet, tengas endpoints de login/checkout/API y te importe no despertarte por un escaneo global que te tumbe.

Riesgo principal: confiar mal en las cabeceras forwarded. Si tratas cualquier X-Forwarded-For como evangelio, atacantes podrán falsificar “IP cliente real” y eludir límites por IP.

Patrón B: Reverse proxy con módulo WAF (p.ej., ModSecurity/Coraza) → servicios Docker

Esto te da control a nivel de reglas y mantiene los logs locales. También es pesado operativamente: las reglas necesitan ajuste y eres responsable del rendimiento.

Úsalo cuando: el cumplimiento exige controles autogestionados o no puedes poner un edge gestionado delante por razones de negocio.

Riesgo principal: consumo de CPU y falsos positivos si activas niveles paranoia del CRS sin entender tu propio tráfico.

Patrón C: Limitación de tasa a nivel de aplicación dentro del contenedor

Bueno para lógica “un usuario no debe spamear un endpoint” (p.ej., restablecimiento de contraseña). Malo como protección primaria.

Úsalo cuando: necesites límites conscientes de identidad (ID de cuenta, API key) y no puedas expresarlo limpiamente en el proxy.

Riesgo principal: cada réplica limita de forma independiente salvo que uses un almacén compartido (Redis, etc.). Los atacantes simplemente te hacen round-robin.

Lo que recomiendo para la mayoría de tiendas Docker

Haz controles tanto en el edge como en el origen:

  • En el edge: mitigación gruesa de bots y protección volumétrica.
  • En tu reverse proxy: límites por ruta precisos y comprobaciones WAF básicas ajustadas a tu app.
  • En la app: controles dirigidos y conscientes de identidad para flujos sensibles.

Controles en capas. Medidos. Logueados. Revertibles.

La identidad es difícil: las IPs mienten, los usuarios se mueven y el NAT arruina tu día

La mayoría de las interrupciones autoinfligidas por WAF/limites de tasa vienen de una suposición: “La dirección IP equivale a usuario.” En 2026, esa suposición falla lo suficiente como para dañarte.

Por qué los límites por IP siguen importando

Són fáciles y baratos. Detienen a los scanners más perezosos. Reducen la radiación de fondo. Pero no son justicia. Son un instrumento contundente.

Dónde fallan los límites por IP

  • Carrier-grade NAT (redes móviles): miles de dispositivos detrás de unas pocas IPs.
  • Proxies corporativos: toda una oficina aparece como una única dirección. Enhorabuena, acabas de limitar la contabilidad.
  • Relés de privacidad y VPNs: usuarios legítimos pueden parecer “como bots”, y los bots pueden parecer “legítimos”.
  • IPv6: los usuarios pueden tener muchas direcciones; la lógica ingenua por IP puede esquivarse con rotación simple de direcciones.

Identidades mejores que “IP origen”

Elige el identificador más fuerte que puedas validar de forma fiable:

  • API key (mejor para APIs B2B)
  • ID de cuenta (después de auth)
  • Cookie de sesión (cuidado con bots que almacenan cookies)
  • Fingerprint del dispositivo (útil, pero complejo y sensible a privacidad)
  • Subnet de IP (a veces más justo que IP exacta, a veces peor)

Regla práctica: limita por IP para endpoints no autenticados, y cambia a límites basados en identidad cuando el usuario está autenticado. Ahí es donde está la equidad.

Diseñar límites de tasa sensatos que no quemen a usuarios reales

La limitación de tasa no es escoger un número. Es modelar comportamiento: flujos humanos, reintentos de la app, inestabilidad móvil y integraciones de terceros que se comportan como ardillas con cafeína.

Empieza con clases de endpoint, no con un límite global

  • Login: baja tasa, alta sensibilidad. Añade retraso progresivo y controles fuertes de bots.
  • Restablecimiento de contraseña / OTP: tasa muy baja, auditoría estricta.
  • Búsqueda: media-alta, pero protege caches y bases de datos del backend.
  • Assets estáticos: usualmente manejados por CDN; no desperdicies presupuesto de límite de origen aquí.
  • Endpoints bulk de API: permite ráfagas, aplica tasas sostenidas y requiere claves.
  • Webhooks entrantes: limita por identidad de partner, no por IP, y permite reintentos.

Elige un algoritmo que concuerde con tu tráfico

Token bucket suele ser el correcto: permite ráfagas cortas (cargas de página, arranque de app) y limita abuso sostenido. Los límites en ventana fija son fáciles pero causan efectos de precipicio: usuarios bloqueados porque hicieron click “refrescar” en el segundo equivocado.

Haz que las respuestas 429 sean útiles

Si devuelves HTTP 429, incluye Retry-After (segundos) o un esquema de cabeceras de límite de tasa que realmente respetes. Los clientes reales retrocederán. Los clientes malos lo ignorarán, lo cual está bien; tú sigues protegiendo tu backend.

No limites las comprobaciones de salud como si fueran atacantes

Si tu orquestador o monitor externo recibe 429, te has creado un auto-daño: reintentará con más insistencia y ahora estás “DDoS’eado” por tu propia herramienta.

Decide qué significa “bloqueado”

Bloquear puede ser:

  • Rechazo duro (403/429): mejor para comportamientos claramente maliciosos.
  • Desafío suave: CAPTCHA o desafío JS en el edge; útil para tráfico sospechoso pero no cierto.
  • Degradar: servir resultados cacheados/obsoletos, reducir funciones costosas o encolar trabajo.

Afinado del WAF sin superstición

Un conjunto de reglas WAF es una hipótesis. Tu tráfico es el experimento. Trata el afinado como ingeniería, no como folclore.

Ejecuta en modo solo detección primero

Activa el WAF para registrar lo que habría bloqueado. Recopila al menos unos días de tráfico, incluyendo horas pico y jobs por lotes. Luego afina excepciones basadas en evidencia.

Conoce tus zonas de alto falso positivo

  • APIs JSON con payloads anidados: pueden disparar reglas de “anomalía en el body”.
  • GraphQL: las query strings a menudo parecen patrones de inyección.
  • Filtros de búsqueda: los usuarios pegan caracteres extraños; los WAFs entran en pánico.
  • Endpoints de subida de archivos: parsing multipart y umbrales de tamaño.
  • URLs de redirect/callback: URLs codificadas en parámetros parecen sondas SSRF.

Estrategia de excepciones: estrechas, registradas, revisadas

No desactives clases enteras de reglas globalmente porque un endpoint sea ruidoso. Haz excepciones por:

  • Ruta (ruta exacta)
  • Método (POST vs GET)
  • Nombre de parámetro (permite rarezas solo en q para búsqueda, no en redirect)
  • Content-type (JSON vs form)
  • Autenticado vs no autenticado

El rendimiento del WAF importa

La inspección del WAF puede consumir mucha CPU. Si tu proxy está saturado, verás aumento de latencia y luego una ola de timeouts. Eso parece un incidente de app, pero en realidad es tu capa de seguridad estrangulando el host.

Broma #2: La forma más rápida de encontrar una regla WAF no probada es desplegarla un viernes por la tarde. También es la forma más rápida de conocer tu rotación de on-call.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las tareas que ejecutas cuando usuarios reportan bloqueos, los bots aumentan o estás desplegando controles por primera vez. Los comandos están escritos para un host Docker con un contenedor reverse proxy (Nginx/Traefik/HAProxy) y herramientas Linux típicas.

Tarea 1: Confirma qué puertos están realmente expuestos

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
NAMES            IMAGE                 PORTS
edge-nginx       nginx:1.25            0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
app-api          myco/api:2026.01      8080/tcp
app-web          myco/web:2026.01      3000/tcp
redis            redis:7               6379/tcp

Qué significa: solo el proxy edge publica 80/443 al mundo. Los contenedores de app son internos.

Decisión: si ves 0.0.0.0:8080->8080 en un contenedor de app, arréglalo. Los rate limits y WAFs no ayudan si los atacantes evitan el proxy.

Tarea 2: Verifica que el proxy sea el único que escucha en 80/443

cr0x@server:~$ sudo ss -lntp | egrep ':80|:443'
LISTEN 0      4096         0.0.0.0:80        0.0.0.0:*    users:(("docker-proxy",pid=2114,fd=4))
LISTEN 0      4096         0.0.0.0:443       0.0.0.0:*    users:(("docker-proxy",pid=2121,fd=4))

Qué significa: Docker está publicando puertos, probablemente mapeados al contenedor proxy.

Decisión: si otros procesos escuchan esos puertos, puedes tener una ruta de bypass o una colisión de puertos. Arregla antes de afinar reglas WAF.

Tarea 3: Comprueba si confías correctamente en las cabeceras forwarded

cr0x@server:~$ docker exec -it edge-nginx nginx -T 2>/dev/null | egrep -n 'real_ip|set_real_ip_from|X-Forwarded-For' | head -n 30
45:    real_ip_header X-Forwarded-For;
46:    set_real_ip_from 172.18.0.0/16;
47:    real_ip_recursive on;

Qué significa: Nginx está configurado para tratar X-Forwarded-For como IP cliente, pero solo cuando el remitente inmediato está en 172.18.0.0/16 (tu red Docker).

Decisión: bien. Si ves set_real_ip_from 0.0.0.0/0; estás dejando que atacantes falsifiquen su IP y evadan límites por IP. Arregla inmediatamente.

Tarea 4: Confirma qué IP ve la app (la comprobación “por qué los usuarios están agrupados”)

cr0x@server:~$ docker logs --tail=20 app-api
2026-01-03T08:18:22Z INFO request method=GET path=/v1/me remote=172.18.0.5 xff="198.51.100.23"
2026-01-03T08:18:22Z INFO request method=POST path=/v1/login remote=172.18.0.5 xff="203.0.113.10, 172.18.0.1"

Qué significa: el peer TCP de la app es el proxy, pero también recibe X-Forwarded-For. La cadena indica múltiples saltos.

Decisión: asegura que tu framework de app use la lógica de IP cliente correcta y confíe solo en tu proxy. De lo contrario, los rate limits en la app serán injustos o evitables.

Tarea 5: Identifica los mayores consumidores en el edge (por IP) desde los access logs

cr0x@server:~$ sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  9123 203.0.113.77
  5110 198.51.100.23
  4022 203.0.113.10
  1889 192.0.2.44

Qué significa: estas IPs están generando muchas solicitudes. No necesariamente maliciosas; podría ser NAT o un partner.

Decisión: cruza con user-agent, paths y códigos de respuesta antes de bloquear. Alto volumen por sí solo no es culpabilidad.

Tarea 6: Encuentra qué endpoints están disparando 429s

cr0x@server:~$ sudo awk '$9==429 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  644 /v1/login
  201 /v1/search
   88 /v1/password-reset

Qué significa: tu límite actual se alcanza más frecuentemente en login y search.

Decisión: los 429 en login pueden ser buenos (control de bots) o malos (usuarios reales detrás de NAT). Los 429 en search a menudo significan que tu límite es demasiado bajo para el comportamiento de la UI o que el frontend reintenta agresivamente.

Tarea 7: Comprueba si los 429s se correlacionan con un user-agent específico (firma de bot)

cr0x@server:~$ sudo awk '$9==429 {print $12}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  701 "-" 
  133 "python-requests/2.31.0"
   67 "Mozilla/5.0"

Qué significa: muchos 429s tienen user-agent faltante/en blanco, lo cual es común en scripts.

Decisión: puedes añadir límites más estrictos o desafíos para tráfico con user-agent vacío, con cautela para no romper clientes legítimos (alguna tooling corporativa es… minimalista).

Tarea 8: Confirma si tu proxy está limitado por CPU durante la inspección WAF

cr0x@server:~$ docker stats --no-stream edge-nginx
CONTAINER ID   NAME        CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O   PIDS
a1b2c3d4e5f6   edge-nginx  186.45%   612MiB / 2GiB         29.88%    1.8GB / 2.1GB     0B / 0B    38

Qué significa: casi 2 cores están saturados en el contenedor proxy. Eso puede ser parsing WAF, reglas con regex pesadas o simplemente demasiado tráfico.

Decisión: si picos de CPU se alinean con peticiones bloqueadas y latencia, afina reglas WAF (reduce alcance), añade caching/CDN o escala réplicas de proxy detrás de un load balancer.

Tarea 9: Verifica síntomas de conntrack y backlog de sockets (host edge bajo estrés)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 249812
net.netfilter.nf_conntrack_max = 262144

Qué significa: conntrack está cerca de lleno. Nuevas conexiones pueden fallar o tener timeouts extraños.

Decisión: aumenta netamente conntrack max con cuidado, reduce churn de conexiones (keep-alive) y asegura que la limitación de tasa no empuje a clientes a tormentas de reconexión.

Tarea 10: Inspecciona logs WAF para la regla exacta que bloquea usuarios reales

cr0x@server:~$ sudo tail -n 20 /var/log/modsecurity/audit.log
--b7c1f3-A--
[03/Jan/2026:08:20:11 +0000] WQx8cQAAAEAAAB9J 198.51.100.23 54022 172.18.0.10 443
--b7c1f3-B--
GET /v1/search?q=%2B%2B%2B HTTP/1.1
Host: api.example.internal
User-Agent: Mozilla/5.0
--b7c1f3-F--
HTTP/1.1 403
--b7c1f3-H--
Message: Access denied with code 403 (phase 2). Matched "Operator `Rx' with parameter `(?:\bunion\b|\bselect\b)'" against variable `ARGS:q' (Value: '+++') [id "942100"]

Qué significa: la regla 942100 se activó en el parámetro de búsqueda q. El usuario buscó “+++” y fue tratado como intento de inyección SQL.

Decisión: añade una excepción dirigida para ARGS:q en /v1/search (o baja la paranoia para ese endpoint). No desactives todo el conjunto de reglas SQLi globalmente.

Tarea 11: Valida que tu clave de limitación es lo que crees

cr0x@server:~$ docker exec -it edge-nginx nginx -T 2>/dev/null | egrep -n 'limit_req_zone|limit_req' | head -n 40
120: limit_req_zone $binary_remote_addr zone=perip:20m rate=10r/s;
215: location /v1/login {
216:     limit_req zone=perip burst=20 nodelay;
217:     proxy_pass http://app-api:8080;
218: }

Qué significa: la clave es la IP cliente ($binary_remote_addr). Si tu “IP real” está equivocada, esto colapsa muchos usuarios en un mismo bucket.

Decisión: confirma manejo de IP real primero. Si muchos usuarios legítimos comparten IPs (NAT corporativo), mueve límites de login a una combinación IP + cookie o añade límites basados en identidad en la app.

Tarea 12: Reproduce un 429 desde tu estación de trabajo para confirmar comportamiento y cabeceras

cr0x@server:~$ for i in {1..40}; do curl -sk -o /dev/null -w "%{http_code} %{time_total}\n" https://example.com/v1/login; done | tail
200 0.031122
200 0.030881
429 0.000942
429 0.000913
429 0.000901

Qué significa: tras algunas solicitudes, alcanzas el límite y recibes 429s inmediatos.

Decisión: verifica si el 429 incluye Retry-After. Si no, añádelo. Si los clientes golpean más fuerte tras un 429, verás un bucle de retroalimentación.

Tarea 13: Verifica que la aplicación no genere reintentos internos que parezcan abuso

cr0x@server:~$ docker logs --since=10m app-web | egrep -i 'retry|429|too many' | tail -n 20
2026-01-03T08:14:02Z WARN api_call failed status=429 retry_in_ms=200
2026-01-03T08:14:02Z WARN api_call failed status=429 retry_in_ms=200
2026-01-03T08:14:03Z WARN api_call failed status=429 retry_in_ms=200

Qué significa: el frontend está reintentando rápido (200ms) en 429, que es lo opuesto a lo deseable.

Decisión: arregla la lógica de backoff del cliente (exponencial, respeta Retry-After). Si no, tu limitador de tasa se convierte en una tormenta de reintentos.

Tarea 14: Confirma punto de terminación TLS y protocolo (HTTP/2 puede cambiar patrones de carga)

cr0x@server:~$ echo | openssl s_client -alpn h2 -connect example.com:443 2>/dev/null | egrep 'ALPN protocol|Protocol'
Protocol  : TLSv1.3
ALPN protocol: h2

Qué significa: los clientes negocian HTTP/2. Multiplexing puede aumentar la concurrencia de solicitudes por cliente.

Decisión: asegúrate de que tus límites son por identidad y por solicitud, no por conexión. Si solo limitas conexiones, HTTP/2 lo atravesará.

Tarea 15: Revisa rutas de red Docker (asegura que no hay ruta directa a apps desde fuera)

cr0x@server:~$ docker network inspect bridge --format '{{json .IPAM.Config}}'
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]

Qué significa: tus contenedores están en un bridge Docker privado. Eso está bien, pero no confundas “IP privada” con “no accesible”.

Decisión: confirma reglas de firewall que eviten acceso entrante a puertos publicados de contenedores y evita publicar puertos de app por completo.

Guía rápida de diagnóstico

Cuando un usuario real está bloqueado (o tus gráficas parecen un sismógrafo), no empieces editando reglas. Empieza localizando la capa que está causando el daño.

Primero: determina qué tipo de bloqueo es

  • 403: probablemente WAF/ACL/lista de bloqueo.
  • 429: limitación de tasa.
  • 5xx: fallo upstream, sobrecarga o timeouts del proxy.
  • Timeouts cliente: conntrack/backlog/problemas handshake TLS, o proxy con CPU al máximo.

Segundo: encuentra dónde se tomó la decisión

  • Logs/eventos del proveedor de edge (si tienes CDN/WAF).
  • Access logs del reverse proxy: código de estado, tiempo de la petición, tiempo upstream.
  • Logs de auditoría del WAF: ID de regla y objetivo de match.
  • Logs de la app: ¿llegó la solicitud a la app?

Tercero: valida identidad y cabeceras

  • ¿Es correcta la IP cliente (cadena X-Forwarded-For)?
  • ¿Confías solo en proxies conocidos?
  • ¿La clave de rate limit está colapsando muchos usuarios en un solo bucket?

Cuarto: comprueba saturación de recursos

  • CPU y memoria del proxy (la inspección WAF y regex pueden disparar CPU).
  • Conntrack cerca del máximo (churn de conexiones y SYN floods).
  • Latencia upstream (BD/cache) causando tormentas de reintentos.

Quinto: mitiga de forma segura

  • Cambia el WAF a detection-only temporalmente si los falsos positivos son severos.
  • Aumenta la capacidad de burst para endpoints de usuario mientras investigas.
  • Aplica allowlists temporales para IPs de partner conocidas con expiración.
  • Throttlea o desafía clases de tráfico sospechoso (UA vacío, ASNs conocidos malos si tienes confianza).

Tres microhistorias corporativas desde las trincheras

Incidente causado por una suposición equivocada: “Por-IP es por-usuario”

La empresa era mediana, B2C, con una base móvil saludable y un endpoint de login que fue golpeado por credential stuffing. Pusieron un rate limit en Nginx para /login: 5 solicitudes por minuto por IP. Simple. Funcionó instantáneamente en sus pruebas sintéticas. El tráfico de bots bajó y la gráfica se veía civilizada.

Dos días después, soporte abrió un incidente porque los fallos de login se dispararon desde una región específica. Los ingenieros miraron el dashboard del WAF y no vieron nada obvio. El origen estaba devolviendo 429s. “Okay,” dijo alguien, “los bots siguen intentando.” Pero los user-agent eran navegadores normales y las solicitudes tenían tokens CSRF válidos.

La pieza faltante fue la realidad de red: una gran operadora móvil en esa región usaba NAT agresivo. Grandes números de usuarios legítimos compartían una pequeña piscina de IPs de salida. Así que cuando llegó la hora pico, la IP NAT alcanzó el límite. Miles de personas competían efectivamente por cinco logins por minuto.

La solución no fue “quitar la limitación.” Fue “dejar de fingir que IP = humano.” Cambiaron límites no autenticados a ser más altos con burst, añadieron claves de cookie de dispositivo en el edge y aplicaron límites más estrictos tras intentos fallidos repetidos por cuenta. También añadieron un desafío suave para patrones de login sospechosos. El credential stuffing se mantuvo abajo y los logins legítimos se recuperaron.

Optimización que salió mal: cachear lo equivocado en la capa equivocada

Otro equipo quería reducir carga en backend y mejorar latencia. Introdujeron cache agresivo en el reverse proxy para varios endpoints GET de la API. Buena idea en general. Luego se pusieron listos: cachearon también algunas respuestas de error, “para proteger el backend durante picos”. Eso incluyó 429s de limitación de tasa.

Durante un leve aumento de tráfico, un subconjunto de usuarios empezó a recibir 429s. El proxy cacheó esos 429s por 30 segundos. Ahora cada usuario golpeando ese endpoint con esa clave de cache (que incluía un conjunto demasiado amplio de cabeceras) recibía el 429 cacheado, incluso si personalmente estaban por debajo del límite.

El incidente parecía “el limitador de tasa es demasiado estricto”, pero no lo era. La capa de caching amplificó el radio de efecto de un throttle localizado en una interrupción visible para usuarios. Habían creado un destino compartido entre clientes no relacionados.

El rollback fue inmediato: nunca caches 429 en el proxy a menos que tengas un diseño muy deliberado, una key de cache correcta y una razón poderosa. Si tratas de proteger upstreams, usa colas, circuit breakers o sirve contenido stale—no throttles cacheados.

Práctica aburrida pero correcta que salvó el día: despliegue escalonado con detection-only y un kill switch

Un servicio relacionado con pagos tenía requisito de seguridad para añadir controles WAF delante de sus APIs hospedadas en Docker. Ya habían sufrido antes el “actívalo y reza”, así que hicieron algo profundamente poco sexy: una fase de dos semanas en detection-only con una revisión diaria.

Cada mañana, un SRE y un ingeniero de app miraban las reglas más disparadas, luego las comparaban con muestras reales de solicitudes. Etiquetaban cada disparo como “malicioso”, “desconocido” o “falso positivo”, y solo escribían excepciones cuando podían explicar el tráfico. También mantenían un feature flag que podía apagar la enforcement en una recarga de configuración.

El día de enforcement, lo desplegaron por clase de endpoint: rutas admin primero (baja diversidad de usuarios), luego APIs de partner (con identity keys), luego endpoints públicos. Monitorizaron tasas de 403 y 429, y tenían un umbral claro de rollback.

Esa tarde, una integración partner legítima empezó a fallar porque enviaba campos JSON inusualmente largos que disparaban una regla de anomalía de tamaño de body. Como tenían historial de detección y un kill switch, pudieron acotar con confianza una excepción a esa ruta y parámetro de partner, recargar y seguir adelante. Sin drama. Sin war room. Solo trabajo.

Errores comunes: síntoma → causa raíz → solución

1) Usuarios detrás de oficinas/hoteles no pueden iniciar sesión (muchos 429s)

Síntoma: quejas desde redes corporativas; logs muestran muchos 429s desde una sola IP con UAs de navegador normales.

Causa raíz: limitación por IP en endpoints no autenticados; NAT agrupa muchos usuarios.

Solución: aumenta burst, limita por cookie/token de dispositivo y añade límites por cuenta para intentos fallidos dentro de la app. Mantén límites por IP, pero menos punitivos.

2) “Todo está bloqueado” después de activar el WAF

Síntoma: aumento súbito de 403s; la app ve menos solicitudes; logs WAF muestran reglas comunes disparándose en payloads normales.

Causa raíz: enforcement activado sin baseline de detección; nivel de paranoia demasiado alto; reglas aplicadas a todas las rutas por igual.

Solución: vuelve a detection-only, afina por endpoint, añade exclusiones estrechas y vuelve a habilitar gradualmente.

3) La limitación de tasa parece ineficaz contra bots

Síntoma: carga backend aún alta; throttles por IP se disparan pero el tráfico de bots continúa.

Causa raíz: ataque distribuido con rotación de IP; o el bot usa muchas IPs por debajo del umbral.

Solución: añade límites basados en identidad (API keys, sesiones), desafíos de bot en el edge y límites agregados (cápsulas de concurrencia global) además de caching.

4) Usuarios aleatorios son bloqueados con errores “SQL injection” en búsqueda

Síntoma: 403s en endpoints de búsqueda; logs WAF muestran reglas SQLi en parámetros de consulta.

Causa raíz: reglas CRS genéricas interpretan sintaxis de búsqueda como SQLi.

Solución: reduce el alcance de inspección o desactiva IDs de reglas específicas para ese parámetro/ruta; mantén reglas SQLi para endpoints sensibles.

5) Después de añadir límites, la latencia aumenta y aparecen timeouts

Síntoma: p95 de latencia se dispara; CPU del proxy alta; algunos errores 504/499.

Causa raíz: sobrecarga de inspección WAF o reglas con regex pesadas; también posible churn de conexiones si los clientes reintentan.

Solución: reduce alcance de inspección (solo inspecciona bodies donde sea necesario), optimiza reglas, escala el proxy y aplica backoff cliente sensato respetando Retry-After.

6) Los usuarios pueden eludir límites falsificando X-Forwarded-For

Síntoma: logs muestran IPs cliente muy variables desde el mismo peer TCP; la limitación de tasa no detecta a clientes abusivos.

Causa raíz: el proxy confía en cabeceras forwarded desde orígenes no confiables.

Solución: configura rangos de IP de proxies de confianza solamente; elimina cabeceras forwarded entrantes en el edge público y vuelve a añadirlas tú mismo.

7) Webhooks fallan porque los reintentos de partners son bloqueados

Síntoma: endpoint de webhook devuelve 429; partner reporta eventos perdidos.

Causa raíz: endpoint de webhook limitado por IP; partners reintentan ráfagas tras timeouts.

Solución: limita por identidad de partner (secreto compartido, token), permite ráfagas mayores y procesa de forma asíncrona con claves idempotentes.

8) “Nuestro monitor dice que estamos caídos” pero los usuarios están bien (o viceversa)

Síntoma: checks externos son bloqueados, pero usuarios reales no; o usuarios reales bloqueados y checks no.

Causa raíz: los monitores tienen IPs/cabeceras distintas y son tratados de forma diferente; exceptions WAF para monitores; límites en endpoints de salud.

Solución: trata el monitoring como un cliente de primera clase: IDs estables, reglas de allow explícitas y endpoints separados para checks profundos.

Listas de comprobación / plan paso a paso

Despliegue paso a paso que evita outages autoinfligidos

  1. Bloquea la exposición: solo el proxy publica puertos; contenedores internos.
  2. Normaliza la identidad cliente: configura IPs de proxy de confianza; establece la IP real cliente correctamente; elimina cabeceras falsificables en el edge público.
  3. Activa el logging primero: access logs con tiempos upstream; logs de auditoría WAF; logs estructurados de la app.
  4. Habilita WAF en detection-only: recopila al menos varios días; incluye horas pico y jobs por lotes.
  5. Clasifica endpoints: login, search, checkout, admin, webhooks, APIs bulk. Cada uno tiene su propia política.
  6. Implementa límites por endpoint: empieza con burst generoso; aplica tasa sostenida; devuelve 429 con Retry-After.
  7. Introduce límites basados en identidad: API key/ID de cuenta/sesión tras auth; por-IP solo para protección no autenticada o gruesa.
  8. Construye excepciones de forma estrecha: por path + parámetro + método. Registra cada justificación de excepción.
  9. Añade un kill switch: una flag de configuración para desactivar enforcement o reducir paranoia sin redeploys de apps.
  10. Despliega gradualmente: admin/internal → APIs de partner → endpoints públicos.
  11. Define umbrales alineados con SLO: define qué tasas de 403/429 son aceptables antes de rollback.
  12. Ensaya la respuesta a incidentes: ¿puedes identificar la capa que bloquea en menos de 5 minutos?

Checklist operativo para mantenimiento continuo

  • Revisión semanal de las reglas WAF más disparadas y de los endpoints con más 429.
  • Confirmar configuración de “IP real” tras cualquier cambio de red/CDN.
  • Rastrear falsos positivos como bugs con propietarios y fechas de caducidad en allowlists temporales.
  • Probar flujos de login/búsqueda desde redes móviles y egresos corporativos (o al menos proxies similares).
  • Asegurar que SDKs cliente respetan Retry-After y backoff exponencial.
  • Planear capacidad de CPU para proxy por la inspección WAF; trátalo como una carga real.

Preguntas frecuentes

1) ¿Debería poner el WAF dentro del contenedor de la app?

No, no como control primario. Ponlo en el reverse proxy o en el edge gestionado donde puedas centralizar políticas, registrar consistentemente y evitar deriva por réplica.

2) ¿Es siempre malo limitar por IP?

Es útil y barato. También es injusto. Úsalo como una cáscara exterior gruesa, luego usa identidades mejores (cookie/sesión/API key/ID de cuenta) cuando sea posible.

3) ¿Cómo evito bloquear usuarios móviles detrás de NAT?

Permite ráfagas, no pongas topes minúsculos por minuto y cambia a una clave basada en cookie/sesión tan pronto como puedas. Para login, añade límites por cuenta para intentos fallidos.

4) ¿Cuál es la diferencia entre 403 y 429 para estos controles?

Usa 429 para “reduce la velocidad, estás superando cuota.” Usa 403 para “no serviremos esta solicitud” (WAF/ACL). Mezclarlos confunde a clientes y operadores.

5) ¿Por qué cambiar HTTP/2 afectó mi comportamiento de limitación de tasa?

HTTP/2 permite muchas solicitudes paralelas sobre una conexión. Si contabas conexiones para estimar carga, HTTP/2 rompe ese modelo. Limita por identidad/solicitud en su lugar.

6) ¿Puede un WAF detener credential stuffing?

No de forma fiable por sí solo. El credential stuffing se parece a tráfico normal de login. Necesitas rate limiting, controles por cuenta, detección de bots y idealmente MFA/score de riesgo.

7) ¿Cómo sé si mi WAF está causando latencia?

Mira CPU del proxy, tiempo de procesamiento de solicitudes y si la latencia sube mientras el tiempo upstream está plano. Los logs de auditoría WAF y los campos de timing del proxy ayudan a correlacionar.

8) ¿Cuál es la forma más segura de añadir excepciones por falsos positivos?

Acótalas estrechamente: por ruta, método y nombre de parámetro; mantén la excepción lo más pequeña posible; añade un propietario y una fecha de revisión.

9) ¿Debería bloquear por user-agent?

Como señal, sí; como identidad única, no. Los user-agents son triviales de falsificar. Pero UAs vacíos u obvios de scripts pueden justificar límites más estrictos o desafíos.

10) ¿Cómo evito que atacantes falsifiquen la IP cliente vía X-Forwarded-For?

Confía en las cabeceras forwarded solo desde rangos de IP de proxies conocidos. Elimina cabeceras forwarded entrantes en el edge público y establece tus propias cabeceras canónicas.

Conclusión: próximos pasos que realmente reducen el ruido del pager

Si solo recuerdas una cosa: la limitación de tasa y los WAFs son características de producción. Necesitan observabilidad, despliegue escalonado y ajuste continuado, igual que bases de datos y pipelines de despliegue.

Haz esto a continuación, en orden:

  1. Asegura que solo el proxy edge está expuesto a internet. No haya puertos de bypass.
  2. Corrige el manejo de la IP cliente real y los límites de confianza de cabeceras forwarded.
  3. Implementa límites por endpoint con bursts y 429 + Retry-After.
  4. Ejecuta el WAF en detection-only, afina por logs y luego aplica enforcement gradualmente.
  5. Añade límites basados en identidad tras la autenticación; deja de depender de la IP como “usuario”.
  6. Mantén un kill switch y un umbral de rollback. Tu yo del futuro te lo agradecerá.

El objetivo no es “bloquear atacantes.” Eso es marketing. El objetivo real es: mantener el servicio sano mientras permites que usuarios legítimos hagan cosas normales. El resto es ajuste y humildad.

← Anterior
Impresoras entre oficinas: solucionar «Lo veo pero no imprime» sobre VPN
Siguiente →
Antes de NVIDIA: qué significaba «gráficos» cuando el 3D era un lujo

Deja un comentario