Skip to content
← Все статьи

Защита API от перегрузки: token bucket, 429, Retry-After

API Rate Limiting: token bucket, 429, Retry-After

rate limiting — это ограничение частоты запросов к API документацию на одного клиента. Цели: предотвратить брутфорс auth-эндпоинтов, защитить от DDoS, сохранить инфраструктуру от злоупотребления тяжёлыми запросами, справедливо распределить ресурсы между тарифы. В статье разберём алгоритмы (token bucket, sliding window), HTTP 429 + Retry-After и реализации на Redis, nginx, Express.

Зачем rate limiting

  • Защита от credential stuffing (автоперебор паролей)
  • Защита от scraping и data exfiltration
  • Контроль затрат: Google Maps API, OpenAI — платные, и без лимита счёт улетит в космос
  • Бизнес-дифференциация: free=100/день, pro=10000/день
  • Fair-use: один клиент не может заблокировать сервис для всех

Алгоритмы

Fixed Window

Счётчик сбрасывается каждую минуту/час. Плюсы: просто. Минусы: «граничный burst» — 100 запросов в 23:59 и ещё 100 в 00:00 = 200 за 2 секунды.

Sliding Window

Учитывает скользящее окно последних N секунд. Точнее, но дороже по памяти. Реализуется через Redis sorted set с timestamp'ами:

ZADD ratelimit:user:123 {now} {now}
ZREMRANGEBYSCORE ratelimit:user:123 0 {now - 60}
ZCARD ratelimit:user:123   ← текущее количество за последние 60 сек
EXPIRE ratelimit:user:123 120

Token Bucket

Бакет вмещает N токенов, пополняется со скоростью M/сек. Каждый запрос забирает 1 токен. Если пусто — 429. Хорошо работает для burst-трафика (кэш-прогрев, массовую проверку URL-запрос).

Leaky Bucket

Обратный к token bucket: запросы заливаются в очередь фиксированной скоростью. Smooth traffic, но не даёт burst'ов.

HTTP 429 и Retry-After

При превышении лимита возвращайте:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705324800
Content-Type: application/json

{"error":"rate_limit_exceeded","message":"Try again in 30 seconds"}

Retry-After — в секундах или как HTTP-дата. Клиенты (curl, браузеры) умеют уважать этот заголовок с backoff.

Реализация на Redis (PHP)

function checkRateLimit(Redis $r, string $key, int $max, int $window): bool {
    $now = microtime(true);
    $r->zAdd("rl:{$key}", $now, "{$now}");
    $r->zRemRangeByScore("rl:{$key}", 0, $now - $window);
    $count = $r->zCard("rl:{$key}");
    $r->expire("rl:{$key}", $window * 2);
    return $count <= $max;
}

if (!checkRateLimit($redis, "user:{$userId}", 60, 60)) {
    header('HTTP/1.1 429 Too Many Requests');
    header('Retry-After: 60');
    exit(json_encode(['error' => 'rate_limit_exceeded']));
}

nginx

limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

server {
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://backend;
    }
    location /api/auth/login {
        limit_req zone=login burst=3 nodelay;
        proxy_pass http://backend;
    }
}

Express / NestJS

// Express
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';

app.use('/api/', rateLimit({
  store: new RedisStore({ client: redisClient }),
  windowMs: 60_000,
  max: 60,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  keyGenerator: (req) => req.user?.id || req.ip,
}));

// NestJS
ThrottlerModule.forRoot({
  throttlers: [{ ttl: 60_000, limit: 60 }],
  storage: new RedisThrottlerStorage(redis),
})

Ключ rate limit: IP vs user vs API key

  • IP — ловит анонимные атаки, но NAT даёт много false-positive в офисах
  • User ID — точнее, если клиент залогинен
  • API key — для публичного API, позволяет дифференцированные тарифы

Лучшая практика — комбинация: user_id || api_key || ip.

Auth-эндпоинты: особые правила

Login/register/password-reset: максимум 5 попыток в 15 минут per IP + per email. Используйте задержку bcrypt (cost 12+), добавьте CAPTCHA после 3 неудач.

Мониторинг

Следите за метриками: процент 429, топ-10 IP по 429, latency при rate limit. Рекомендую настроить мониторинг на rising 429 rate — это сигнал о DDoS или сломанном клиенте.

FAQ

Чем отличается 429 от 503? 429 — клиент превысил свой лимит, 503 — сервер перегружен независимо от клиента.

Нужен ли Retry-After? Очень желательно — без него клиенты делают busy-loop, ухудшая ситуацию.

Как защититься от распределённого DDoS? Rate limit на приложении не хватит — нужен Cloudflare/AWS Shield или WAF.

Можно ли обойти rate limit? Через ротацию IP (прокси), ротацию API-ключей, бесплатные триалы. Защита — фингерпринтинг + behavioural analysis.

Вывод

Минимум: sliding-window на Redis, 429+Retry-After, отдельный жёсткий лимит на auth-эндпоинты. Для серьёзных угроз — WAF. Мониторьте API через enterno monitors. Связанное: CORS, HTTP Security Headers.

Проверьте ваш сайт прямо сейчас

Проверить →
Другие статьи: SEC
SEC
Защита от Clickjacking: X-Frame-Options vs frame-ancestors
15.04.2026 · 69 просм.
SEC
Безопасность cookie: HttpOnly, Secure, SameSite, __Host-
15.04.2026 · 73 просм.
SEC
Миграция с HTTP на HTTPS: редиректы, mixed content, HSTS
15.04.2026 · 60 просм.
SEC
Subresource Integrity (SRI): защита CDN-скриптов
15.04.2026 · 61 просм.