La limitación de tasa es un control de seguridad, no un contador

Tue Sep 08 2026

La limitación de tasa es un control de seguridad, no un contador

Así que, antes de nombrar cinco algoritmos, hazte la primera pregunta: ¿qué intentas suavizar exactamente y las solicitudes de quién estás contando?

Así que empieza con un endpoint público de restablecimiento de contraseña. Puede activar trabajo en la base de datos, el envío de correos electrónicos y riesgo de enumeración de cuentas. Los llamadores autenticados pueden tener identidades de cuenta estables; los llamadores anónimos pueden compartir una dirección NAT empresarial; una botnet puede distribuir solicitudes entre miles de IP.

Así que elige el limitador cuyo modo de fallo puedas asumir. Esta guía va desde el recurso y el modelo de amenazas hasta el comportamiento de las ráfagas, la identidad, el estado distribuido, la gestión de la sobrecarga y la señalización HTTP.

En este artículo

La limitación de tasa comienza con una identidad y un presupuesto de fallos

RFC 6585 define 429 de esta manera: «El código de estado 429 indica que el usuario ha enviado demasiadas solicitudes en un periodo de tiempo determinado (“limitación de tasa”)». El protocolo te proporciona el código de estado y deja las decisiones peligrosas en manos de tu diseño.

Así que elige el principal, el ámbito del conteo y el comportamiento ante fallos. HTTP 429 no elige ninguno de ellos por ti. El RFC dice explícitamente que no define cómo un origen identifica a un usuario ni cuenta las solicitudes.

Anota estas tres respuestas antes de seleccionar un algoritmo:

  1. ¿Qué recurso o comportamiento estás protegiendo?
  2. ¿Qué ráfagas son legítimas?
  3. ¿Qué identidad le resulta costoso multiplicar al atacante?

Así que, para el endpoint de restablecimiento de contraseña, la acción costosa puede ser enviar mensajes de restablecimiento o probar si existen cuentas. Los límites por cuenta pueden proteger identidades autenticadas o identificadores de cuenta solicitados. Los límites por IP proporcionan una señal independiente para el tráfico anónimo. Las cuentas comprometidas siguen siendo un problema aparte: un atacante que controla cuentas válidas puede superar una comprobación basada en la clave de cuenta.

Pero la limitación por IP solo es un recurso anónimo alternativo útil. Una empresa puede colocar a muchos usuarios legítimos detrás de una dirección, mientras que una botnet puede multiplicar las direcciones de origen a bajo coste.

Y cada solicitud actualiza el estado: un contador, un saldo de tokens, un conjunto de marcas de tiempo o un plazo virtual. En un despliegue distribuido, la precisión, la disponibilidad, el almacenamiento, el comportamiento del reloj y la consistencia pasan a formar parte del diseño de seguridad.

Los cinco algoritmos difieren principalmente en lo que permiten en los límites

Así que céntrate en el comportamiento que produce cada algoritmo. Lee primero la columna de ráfagas; ahí es donde se encuentran la capacidad y el riesgo de abuso.

AlgoritmoEstadoComportamiento de las ráfagasFortaleza principalPrincipal modo de fallo
Ventana fijaO(1)Hasta 2× a través de un límiteImplementación más sencillaRáfaga en el límite
Registro de ventana deslizanteO(N) marcas de tiempoLímite móvil exactoExactitudCrecimiento de memoria
Contador de ventana deslizanteO(1)Límite móvil aproximadoCompromiso compactoDeriva de la estimación
Token bucketO(1)Capacidad de ráfaga explícitaTasa media más ráfagasLa ráfaga puede sobrecargar los sistemas posteriores
Leaky bucketEstado virtual O(1); O(cola) con solicitudes en colaSalida fluidaTasa de liberación controladaSemántica de cola y rechazo

La seguridad depende tanto de la clave y de la ruta de fallo como del algoritmo. La tabla presenta a las ventanas fijas su argumento más sólido —la simplicidad— y deja expuesto su coste en el límite.

Las ventanas fijas intercambian precisión por un estado económico

Divide el tiempo en intervalos fijos, como un minuto, y mantén un contador para cada clave. Un contador de ventana fija necesita un estado O(1) por clave; Redis puede implementarlo con una operación atómica de contador y expiración.

Para un límite de 100 solicitudes por ventana de 60 segundos, un intervalo móvil de 60 segundos que atraviese el límite de la ventana fija puede contener hasta 200 solicitudes: 100 justo antes del reinicio y 100 justo después. Ese es el comportamiento definido. No es un misterioso error de implementación. Usa ventanas fijas cuando la simplicidad sea importante y la capacidad de los sistemas posteriores pueda absorber la ráfaga del límite.

Las ventanas deslizantes conservan más historial o lo estiman

Un registro de ventana deslizante almacena la marca de tiempo de cada solicitud de una clave. En cada solicitud, elimina las marcas de tiempo anteriores al intervalo, cuenta las restantes y acepta o rechaza la nueva solicitud. La semántica es exacta. El precio es un estado O(N).

Las claves de alta frecuencia multiplicadas entre muchos clientes pueden hacer que el historial de marcas de tiempo resulte costoso, incluso en un conjunto ordenado de Redis. Úsalo cuando importe el comportamiento móvil exacto y el volumen de tráfico y la cardinalidad de las claves hagan que el historial sea asumible.

Un contador de ventana deslizante conserva únicamente los conteos de la ventana anterior y la actual. Estima el total móvil ponderando el conteo anterior:

estimate = previous × (1 − elapsed / window) + current

Aquí, elapsed se mide desde el inicio de la ventana actual, y elapsed y window deben usar las mismas unidades, como milisegundos. A mitad de una ventana de un minuto, la mitad del conteo anterior contribuye a la estimación.

El método usa un estado O(1), pero el tráfico agrupado al final de la ventana anterior puede hacer que la estimación se desvíe. Cloudflare describe el atractivo de dos números y una aritmética sencilla. Un contador de ventana deslizante es un compromiso práctico cuando los límites de las ventanas fijas son demasiado laxos y el historial exacto de marcas de tiempo cuesta demasiado.

Los token buckets admiten ráfagas por diseño

Un token bucket se rellena a una tasa r y contiene como máximo b tokens. Cada solicitud consume un token. Los tokens no utilizados crean permiso para una ráfaga inmediata.

La tasa de relleno controla el promedio a largo plazo; la capacidad controla la ráfaga instantánea. Un bucket configurado para 10 tokens por segundo y con una capacidad de 20 puede admitir 20 solicitudes inmediatamente cuando está lleno, suponiendo un token por solicitud. Después, los tokens regresan a razón de 10 por segundo.

Este modelo se adapta a las lecturas normales de API y a los picos de tráfico. Stripe usa token buckets para la limitación de tasa de API porque el modelo mantiene estable la tasa media al tiempo que permite ráfagas.

Token bucket proporciona la misma seguridad que una ventana deslizante únicamente cuando su permiso deliberado para ráfagas encaja con el modelo de amenazas. Para trabajos de restablecimiento de contraseña o relacionados con el inicio de sesión, establece la capacidad según la tolerancia de los sistemas posteriores y el riesgo de abuso. Una política general de API puede permitir demasiado.

Los leaky buckets suavizan la liberación en lugar de la admisión

Un token bucket controla la admisión. Un leaky bucket controla la tasa a la que se libera o procesa el trabajo aceptado. Esa distinción explica su diferente comportamiento ante las ráfagas.

Una implementación basada en una cola almacena el trabajo encolado, mientras que GCRA representa el calendario de forma compacta; elige una implementación probada en lugar de asumir un comportamiento equivalente.

nginx documenta un limitador de tasa de solicitudes con rate, burst y un comportamiento de retraso opcional. Con rate=1r/s y burst=5, nginx permite cinco solicitudes por encima de la tasa estable. Esas solicitudes se incorporan a la ráfaga permitida. Con nodelay, nginx libera la ráfaga inmediatamente, aunque sigue aplicando la tasa configurada a lo largo del tiempo. nginx rechaza las solicitudes excesivas con 503 de forma predeterminada, en lugar de 429.

Elige según el fallo de los sistemas posteriores que puedas tolerar, no según el diagrama que reconozcas.

El tráfico normal expone una compensación que los diagramas ocultan

Un experimento reproducible de Yuhi-sa utilizó estos ajustes:

  • Límite de ventana: 10 solicitudes por segundo
  • Token bucket: 10 tokens por segundo, capacidad 20
  • Leaky bucket: 10 solicitudes por segundo, capacidad 20
  • Tráfico: llegadas de Poisson a 6 solicitudes por segundo
  • Duración: 30 segundos
  • Solicitudes: 184
  • Semilla aleatoria: 42

Los resultados fueron:

  • Ventana fija: 178 permitidas, 6 rechazadas, es decir, 96.74%
  • Contador de ventana deslizante: 178 permitidas, 6 rechazadas, es decir, 96.74%
  • Token bucket: 184 permitidas, 0 rechazadas, es decir, 100%
  • Leaky bucket: 184 permitidas, 0 rechazadas, es decir, 100%

Al 60% de la tasa media configurada, las llegadas de Poisson siguen agrupándose. La capacidad del bucket absorbe esa variación. Los métodos basados en ventanas rechazan algunas solicitudes agrupadas porque sus contadores detectan picos locales.

Esto demuestra una carga de trabajo, una configuración, una duración y una semilla. A partir de ello no se puede inferir la capacidad de producción ni el impacto en los usuarios; no dice nada sobre la latencia de tu almacenamiento, el coste del endpoint o el patrón de reintentos del cliente. Mide esos aspectos por separado.

Los errores más difíciles suelen estar relacionados con la identidad, la sobrecarga y la semántica

«¿Por qué mi límite de 100 por minuto permitió 200?»

El límite de la ventana fija hizo exactamente aquello para lo que fue construido. Acepta ese comportamiento cuando el limitador sea un control de equidad aproximado y la capacidad de los sistemas posteriores pueda absorber la ráfaga del límite. Usa un contador de ventana deslizante para disponer de un estado de tamaño constante con menos distorsión en los límites, o un registro cuando el historial exacto justifique su coste de memoria.

«¿Por qué la protección por IP no detiene el ataque?»

La limitación por IP es un recurso anónimo alternativo útil para el tráfico basado en red.

BackendBytes describe un limitador de ventana fija de 100 solicitudes por minuto basado en la IP que un atacante evadió distribuyendo el tráfico entre 1.000 direcciones IP.

El fallo opuesto es igual de real. Una gran empresa puede enviar a muchos titulares legítimos de cuentas a través de una dirección NAT, limitando conjuntamente a usuarios no relacionados. Para el tráfico autenticado, basa el límite principal en una cuenta o identidad de usuario estable. Para el tráfico anónimo, la IP puede ser una señal.

Para el endpoint de restablecimiento de contraseña, usa límites independientes por cuenta o identificador solicitado junto con límites basados en señales de red. Esto suele ser más seguro que sustituir ambos por una única clave compuesta: cambiar cualquiera de los componentes no debería eliminar el otro control. Prueba la política frente a colisiones de NAT y ataques distribuidos antes de considerarla protectora.

Los límites basados en la clave de cuenta también tienen un límite: una cuenta comprometida puede realizar solicitudes con apariencia legítima. El limitador reduce una vía de abuso; no demuestra que el llamador sea benigno.

«¿Por qué el limitador aumentó el trabajo durante el ataque?»

Un servidor en la capa de aplicación puede seguir teniendo que recibir la solicitud. Después ejecuta el limitador, construye una respuesta y la envía. Durante un ataque, generar una respuesta para cada solicitud rechazada añade trabajo al sistema que intentas proteger.

RFC 6585 permite descartar conexiones cuando responder a cada solicitud consumiría demasiados recursos. Eso no hace que descartar conexiones sea universalmente preferible. El filtrado en el edge, la eliminación de conexiones o los controles ascendentes pueden tener que actuar antes de generar un 429 en la capa de aplicación.

Considera tus controles de protección contra DDoS y de firewall de aplicaciones web como parte de la misma ruta de fallo, si esos controles están presentes en tu despliegue.

«¿Por qué mi cliente reintentó lo incorrecto?»

Porque Retry-After es opcional, los clientes deben definir un fallback. Reserva el procedimiento de reintento real para el contrato HTTP que aparece a continuación.

«¿Por qué la CDN sirvió una respuesta de limitación de tasa?»

RFC 6585 dice que las respuestas 429 NO DEBEN almacenarse en caché. Verifica que las capas de CDN y proxy respeten esa regla, especialmente cuando las respuestas contengan detalles específicos de una cuenta o de un reintento.

La corrección distribuida cuesta más que añadir Redis

Las compensaciones de los algoritmos son explicables, pero los límites seguros no pueden elegirse solo a partir de la teoría. Mide el coste del endpoint, el tamaño de las ráfagas legítimas y el comportamiento del almacén del limitador con tu tráfico.

Un contador en memoria funciona cuando un único proceso toma todas las decisiones de aplicación, o cuando los límites por instancia son intencionados. Con varias instancias de API, cada proceso ve solo una parte del tráfico a menos que el estado se comparta o la política esté particionada deliberadamente.

Usa esta secuencia:

  1. Elige la clave. Documenta la identidad, el comportamiento alternativo y el límite de confianza.
  2. Elige el modelo de estado. Los contadores, las estimaciones de dos ventanas, las marcas de tiempo, los saldos de tokens y los plazos virtuales tienen diferentes costes de almacenamiento.
  3. Usa un estado compartido. Redis o un almacén equivalente evita que los puntos de aplicación diverjan.
  4. Haz que la decisión sea atómica. La comprobación y la actualización deben ocurrir juntas. Los scripts Lua de Redis son un enfoque práctico habitual; las operaciones atómicas del servidor u otras primitivas equivalentes pueden cumplir la misma función.
  5. Define el comportamiento ante fallos. Decide qué ocurre cuando el almacén está lento o no disponible: fail open, fail closed o un control de emergencia más amplio. Define también la expiración, la gestión del reloj y el comportamiento regional.
  6. Mide el limitador. Registra la latencia de las decisiones, los errores del almacén, las solicitudes rechazadas y la cardinalidad de las claves.

Para la semántica de leaky bucket, GCRA puede representar compactamente un calendario virtual. Usa una implementación probada; la elección del algoritmo no establece su rendimiento ni su corrección para tu almacén.

Los clientes necesitan un contrato de limitación de tasa que puedan cumplir

Un código de estado forma parte de la superficie de ataque cuando el atacante controla cuántas solicitudes rechazadas procesas.

Un servidor que devuelve 429 debería incluir una explicación útil y no sensible. RFC 6585 dice que la respuesta debe contener detalles y puede incluir Retry-After.

Retry-After tiene dos formatos

El encabezado puede contener segundos de espera:

Retry-After: 3600

Eso significa 3.600 segundos. También puede contener una fecha HTTP:

Retry-After: Wed, 21 Oct 2015 07:28:00 GMT

Los clientes deben analizar ambos formatos, tal como se documenta en la referencia de Retry-After de MDN.

Una política de cliente debería:

  1. Respetar un Retry-After válido.
  2. En caso contrario, usar exponential backoff.
  3. Añadir full jitter.
  4. Limitar el retraso máximo.
  5. Dejar de reintentar cuando la operación ya no resulte útil.

Muchas API exponen los campos de facto X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset. Los campos RateLimit-* del borrador del IETF pretenden estandarizar información equivalente, pero el despliegue sigue siendo desigual. Documenta los campos que tu servicio admite realmente.

El comportamiento documentado de limit_req de nginx emite 503 para las solicitudes excesivas rechazadas de forma predeterminada. Los clientes que reciben 503 pueden reintentar como si el servicio no estuviera disponible, mientras que los clientes que reciben 429 aplican un comportamiento de limitación de tasa. Prueba la respuesta en el edge, no solo en el código de la aplicación.

Elige el modo de fallo que puedas asumir

  • Ventana fija: elige el control más sencillo posible y acepta su comportamiento en los límites.
  • Registro de ventana deslizante: elige una semántica móvil exacta cuando el estado de marcas de tiempo sea asumible.
  • Contador de ventana deslizante: elige una aproximación O(1) cuando sea necesario corregir los límites de las ventanas fijas.
  • Token bucket: elige un promedio a largo plazo con ráfagas legítimas.
  • Leaky bucket: elige un procesamiento fluido y verifica si la implementación encola o rechaza.

Antes de ponerlo en producción, responde a cinco preguntas:

  1. ¿La clave es una identidad o simplemente una ubicación de red?
  2. ¿Qué ráfaga permite el algoritmo?
  3. ¿Dónde se almacena el estado compartido y la actualización es atómica?
  4. ¿Qué ocurre cuando el limitador o el almacén están sobrecargados?
  5. ¿Coinciden el servidor y el cliente en el estado, Retry-After, los encabezados, el almacenamiento en caché y los reintentos?

Registra el tipo de clave, el algoritmo, la decisión, el permiso restante, la latencia del almacén y el estado de respuesta para un conjunto muestreado de solicitudes. Si no puedes explicar un rechazo, el limitador no está listo.