Финансовый риск
До 500 000 рублей в час ввиду парализации внутренних интеграций и сбоя в работе мобильных приложений. Риск каскадного отказа всех связанных сервисов предприятия.
Влияние на KPI
Недоступность мобильного приложения для клиентов. Рост процента ошибок API (5xx). Снижение LTV и удовлетворенности пользователей из-за медленной работы интерфейсов.
Уровень критичности
Высокий
Кому поручить
Backend-разработчик / Архитектор API / DevOps-инженер
Slug: api-rate-limiting-guide
Кластер: Базовая защита
Время чтения: 12 мин
Meta
- SEO Title: API Rate Limiting — полный гайд по защите API от DDoS и abuse
- SEO Description: Как настроить rate limiting для REST API: примеры для nginx, Node.js, Python, Go. Защита от брутфорса, credential stuffing и L7-атак.
- OG Title: API Rate Limiting: защита от abuse и DDoS
- OG Description: Практический гайд по настройке rate limiting для API с примерами кода для nginx, Express, Flask и Go.
Введение
Rate Limiting — первая линия защиты API от:
- L7-атак (HTTP flood)
- Брутфорса и credential stuffing
- Агрессивного парсинга
- Случайного abuse (баги в клиентах)
Без rate limiting один клиент может положить весь сервис. С ним — получит 429 Too Many Requests и подождёт.
Стратегии Rate Limiting
1. Fixed Window
Самый простой: N запросов за период (минута/час).
┌─────────────────────────────────────┐
│ 12:00-12:01 │ 100 запросов max │
│ 12:01-12:02 │ 100 запросов max │
└─────────────────────────────────────┘
Проблема: burst на границе окон (200 запросов за 2 секунды).
2. Sliding Window
Скользящее окно — считает запросы за последние N секунд.
Сейчас: 12:00:30
Окно: 12:00:30 - 11:59:30 (последние 60 сек)
Лучше для защиты, но требует больше памяти.
3. Token Bucket
Токены накапливаются со временем, тратятся на запросы.
Bucket: 100 токенов max
Refill: 10 токенов/сек
Запрос: -1 токен
Идеально для API — позволяет burst, но ограничивает sustained load.
4. Leaky Bucket
Запросы в очередь, обрабатываются с фиксированной скоростью.
Хорош для выравнивания нагрузки, но добавляет latency.
Nginx: базовый Rate Limiting
Конфигурация
# /etc/nginx/nginx.conf
http {
# Зона лимитов: 10MB памяти, ключ = IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# Для авторизации — строже
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=1r/s;
# По API-ключу (если есть)
limit_req_zone $http_x_api_key zone=apikey_limit:10m rate=100r/s;
}
Применение к location
server {
# Общий API
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
# Авторизация — строго
location /api/auth/ {
limit_req zone=auth_limit burst=5;
limit_req_status 429;
# Добавляем задержку при превышении
limit_req zone=auth_limit burst=5 delay=3;
proxy_pass http://backend;
}
# Публичные данные — мягче
location /api/public/ {
limit_req zone=api_limit burst=50 nodelay;
proxy_pass http://backend;
}
}
Параметры
Node.js / Express
express-rate-limit
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// Общий лимит API
const apiLimiter = rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 60 * 1000, // 1 минута
max: 100, // 100 запросов
standardHeaders: true, // RateLimit-* headers
legacyHeaders: false,
message: {
error: 'Too many requests',
retryAfter: 60
},
keyGenerator: (req) => {
// По API-ключу или IP
return req.headers['x-api-key'] || req.ip;
}
});
// Строгий лимит для auth
const authLimiter = rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 15 * 60 * 1000, // 15 минут
max: 5, // 5 попыток
skipSuccessfulRequests: true, // Не считать успешные
message: {
error: 'Too many login attempts',
retryAfter: 900
}
});
// Применение
app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);
Динамические лимиты по плану
const getPlanLimits = (apiKey) => {
const plans = {
free: { rpm: 60, burst: 10 },
pro: { rpm: 1000, burst: 100 },
enterprise: { rpm: 10000, burst: 500 }
};
// Получить план из БД по apiKey
return plans[getUserPlan(apiKey)] || plans.free;
};
const dynamicLimiter = rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 60 * 1000,
max: (req) => {
const limits = getPlanLimits(req.headers['x-api-key']);
return limits.rpm;
},
keyGenerator: (req) => req.headers['x-api-key'] || req.ip
});
Python / Flask
flask-limiter
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(
app=app,
key_func=get_remote_address,
storage_uri="redis://localhost:6379",
default_limits=["100 per minute"]
)
# Базовый endpoint
@app.route("/api/data")
@limiter.limit("100/minute")
def get_data():
return {"data": "..."}
# Строгий лимит для auth
@app.route("/api/auth/login", methods=["POST"])
@limiter.limit("5/15 minutes")
def login():
return {"token": "..."}
# Разные лимиты по роли
def get_limit_by_role():
api_key = request.headers.get("X-API-Key")
if is_premium(api_key):
return "1000/minute"
return "100/minute"
@app.route("/api/premium")
@limiter.limit(get_limit_by_role)
def premium_endpoint():
return {"premium": True}
# Обработка 429
@app.errorhandler(429)
def ratelimit_error(e):
return {
"error": "Rate limit exceeded",
"retry_after": e.description
}, 429
Go
golang.org/x/time/rate
package main
import (
"net/http"
"sync"
"time"
"golang.org/x/time/rate"
)
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu *sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
mu: &sync.RWMutex{},
r: r,
b: b,
}
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter, exists := i.ips[ip]
if !exists {
limiter = rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
}
return limiter
}
// Middleware
func RateLimitMiddleware(limiter *IPRateLimiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if !limiter.GetLimiter(ip).Allow() {
w.Header().Set("Retry-After", "60")
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
// 10 запросов/сек, burst 20
limiter := NewIPRateLimiter(rate.Limit(10), 20)
mux := http.NewServeMux()
mux.HandleFunc("/api/", handleAPI)
http.ListenAndServe(":8080", RateLimitMiddleware(limiter)(mux))
}
Redis для распределённого Rate Limiting
При нескольких серверах нужен общий счётчик:
-- rate_limit.lua (Redis Lua script)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, window)
end
if current > limit then
return 0 -- Rejected
end
return 1 -- Allowed
// Node.js
const checkRateLimit = async (key, limit, windowSec) => {
const result = await redis.eval(
rateLimitScript,
1,
`ratelimit:${key}`,
limit,
windowSec
);
return result === 1;
};
Headers для клиентов
Стандарт RFC 6585:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1640995200
{
"error": "Rate limit exceeded",
"retry_after": 60
}
Защита от обхода
1. Не только по IP
const keyGenerator = (req) => {
const factors = [
req.ip,
req.headers['x-api-key'],
req.headers['user-agent'],
req.user?.id
].filter(Boolean);
return crypto.createHash('sha256')
.update(factors.join(':'))
.digest('hex');
};
2. Прогрессивные задержки
const getDelay = (attempts) => {
// 0, 1, 2, 4, 8, 16... секунд
return Math.min(Math.pow(2, attempts - 1), 60) * 1000;
};
3. CAPTCHA при подозрении
if (attempts > 3 && !req.captchaVerified) {
return res.status(429).json({
error: 'CAPTCHA required',
captcha_url: '/api/captcha'
});
}
Мониторинг
Prometheus метрики
const rateLimitCounter = new prometheus.Counter({
name: 'api_rate_limit_hits_total',
help: 'Rate limit hits',
labelNames: ['endpoint', 'status']
});
// В middleware
if (rateLimitExceeded) {
rateLimitCounter.inc({ endpoint: req.path, status: 'rejected' });
} else {
rateLimitCounter.inc({ endpoint: req.path, status: 'allowed' });
}
Grafana алерты
# Alert при >5% rejected запросов
- alert: HighRateLimitRejection
expr: |
sum(rate(api_rate_limit_hits_total{status="rejected"}[5m]))
/
sum(rate(api_rate_limit_hits_total[5m]))
> 0.05
for: 5m
annotations:
summary: "High rate limit rejection rate"
Чек-лист внедрения
Связанные материалы
Нужна помощь с настройкой? Консультация →
Практическая экспертиза AntiDDoS.su
Практическая экспертиза AntiDDoS.su Этот материал подготовлен инженерами безопасности AntiDDoS.su. Мы специализируемся на отражении распределенных атак любой сложности, аудите безопасности инфраструктуры и настройке отказоустойчивых систем. 🛡️ Важная информация: Если ваш ресурс находится под атакой или нуждается в профессиональном аудите защиты — обратитесь к экспертам AntiDDoS.su для оперативной помощи.
Скопируйте эти вопросы и отправьте вашему техническому директору (CTO) или руководителю разработки:
- Внедрен ли паттерн Circuit Breaker для изоляции упавших сервисов и сохранения стабильности остальной системы?
- Лимитируются ли запросы к API по токенам авторизации (JWT) на уровне Ingress/API Gateway?
- Ограничена ли глубина и сложность запросов в GraphQL для защиты от атак на исчерпание памяти?
Словарь по теме
Rate Limiting
Ограничение количества запросов с одного IP-адреса за определённый период времени. Первая линия защиты от ботов, брутфорса и простых DDoS-атак.
User-Agent
HTTP-заголовок, идентифицирующий браузер или программу. Боты часто имеют пустой или подозрительный User-Agent.
Prometheus
Система мониторинга и сбора метрик. Собирает данные с серверов, хранит временные ряды, поддерживает алерты.
L7 атака
Атака на прикладном уровне (HTTP). Атакующий отправляет валидные HTTP-запросы, которые выглядят как обычные пользователи, но нагружают тяжёлые endpoint'ы.
Endpoint
URL-адрес, по которому доступен определённый ресурс или функция API. Например: /api/users, /login.
Latency
Время отклика — задержка между отправкой запроса и получением ответа. Измеряется в миллисекундах. Чем меньше — тем лучше.
CAPTCHA
Тест для отличия человека от бота. Просит распознать изображения, решить задачу или просто кликнуть галочку. Используется для защиты форм.
Grafana
Платформа визуализации метрик. Строит дашборды с графиками из Prometheus, InfluxDB и других источников.