Rate limiting — одна из тех вещей, которую откладывают на потом, пока сайт не ляжет под атакой в пятницу вечером. Я через это прошёл сам и видел это у десятков клиентов — поэтому сегодня разберём настройку ограничения запросов детально, с реальными конфигами и без воды.
Что такое rate limiting и почему это не опционально в 2026
Грубо говоря, rate limiting — это механизм, который ограничивает количество запросов от одного источника за единицу времени. Звучит просто. На деле это многоуровневая система, которую нужно правильно настроить на каждом слое стека: Nginx, PHP, приложение, база данных.
Ландшафт атак в 2026 году изменился кардинально. Ботнеты стали умнее: они имитируют поведение реальных пользователей, распределяют нагрузку по тысячам IP-адресов, меняют User-Agent на лету. Классический DDoS уровня L3/L4 давно перестал быть единственной угрозой. Сейчас куда опаснее L7-атаки — HTTP-флуд, брутфорс форм авторизации, credential stuffing, скрейпинг. И всё это лечится именно правильно настроенным rate limiting.
У меня был клиент — интернет-магазин на Битрикс, MySQL 8.0, примерно 3000 уникальных в сутки. В ноябре 2024 года к ним пришёл HTTP-флуд: 40 000 запросов в минуту на страницу каталога. Сервер лёг за 4 минуты. Хостинг просто отключил сайт. После того как мы настроили rate limiting на уровне Nginx + fail2ban, следующая аналогичная атака была отбита автоматически — сервер даже не почувствовал. Вот зачем это нужно.
Rate limiting в Nginx: базовая и продвинутая настройка
Nginx — первая линия обороны. Именно здесь нужно отсекать большинство нежелательного трафика ещё до того, как запрос дойдёт до PHP или приложения. Nginx умеет ограничивать запросы через директиву limit_req и limit_conn. Это разные вещи: первая ограничивает частоту запросов, вторая — количество одновременных соединений.
Базовая конфигурация для большинства сайтов выглядит так. Сначала объявляем зоны в блоке http, потом применяем их в location. Я обычно делаю несколько зон под разные сценарии: общий трафик, страницы авторизации, API-эндпоинты.
http {
# Зона для общего трафика: 10 запросов в секунду с одного IP
# Буфер на 10MB (~160 000 IP-адресов)
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# Жёсткий лимит для страниц авторизации: 5 запросов в минуту
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# Для API: 30 запросов в секунду
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
# Лимит соединений: не более 20 одновременных с одного IP
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 443 ssl http2;
server_name example.com;
# Применяем общий лимит
limit_req zone=general burst=20 nodelay;
limit_conn conn_limit 20;
# Возвращаем 429 вместо 503 при превышении лимита
limit_req_status 429;
limit_conn_status 429;
location /login {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
# ... остальная конфигурация
}
location /api/ {
limit_req zone=api burst=50 nodelay;
limit_req_status 429;
# ... остальная конфигурация
}
location /admin/ {
# Для админки — максимально жёстко
limit_req zone=login burst=2 nodelay;
# Плюс ограничение по IP, если нужно
allow 192.168.1.0/24;
allow 203.0.113.10; # ваш офисный IP
deny all;
}
}
}
Параметр burst — это допустимый "всплеск" запросов сверх лимита. Если rate=10r/s и burst=20, то пользователь может одномоментно послать 20 запросов, но потом они будут обрабатываться строго по 10 в секунду. nodelay означает, что запросы в рамках burst обрабатываются немедленно, а не ставятся в очередь. Для большинства сайтов — правильный выбор.
Обратите внимание на статус 429 (Too Many Requests) вместо дефолтного 503. Это семантически правильнее и позволяет клиентам (в том числе легитимным API-клиентам) понять, что нужно просто подождать, а не что сервер упал.
Защита форм авторизации и брутфорс
Брутфорс форм авторизации — это, честно говоря, самая частая атака на коммерческие сайты. Боты перебирают пароли методично, иногда по 500-1000 попыток в час с одного IP. Nginx-лимит помогает, но недостаточен без дополнительных слоёв.
На уровне PHP я всегда добавляю счётчик неудачных попыток через Redis. Это работает быстрее, чем писать в MySQL, и не создаёт лишней нагрузки на базу. Вот упрощённая реализация для Laravel (PHP 8.2+), но принцип одинаков для любого фреймворка:
ip();
$key = 'login_attempts:' . $ip;
$attempts = (int) Redis::get($key);
if ($attempts >= self::MAX_ATTEMPTS) {
$ttl = Redis::ttl($key);
return response()->json([
'error' => 'Too many login attempts',
'retry_after' => $ttl,
], 429)->withHeaders([
'Retry-After' => $ttl,
'X-RateLimit-Limit' => self::MAX_ATTEMPTS,
'X-RateLimit-Remaining' => 0,
]);
}
$response = $next($request);
// Если авторизация провалилась — инкрементируем счётчик
if ($response->getStatusCode() === 401 ||
($response->getStatusCode() === 422 && $request->routeIs('login'))) {
Redis::multi();
Redis::incr($key);
if ($attempts === 0) {
// Устанавливаем TTL только при первой попытке
Redis::expire($key, self::DECAY_SECONDS);
}
Redis::exec();
}
return $response;
}
}
На WordPress я использую плагин WP Cerber Security или Wordfence — они делают примерно то же самое, но через собственный механизм. Для Битрикс есть встроенный модуль "Проактивная защита", но его настроек по умолчанию недостаточно — нужно ужесточать вручную. Подробнее об этом можно почитать в разделе поддержки Битрикс.
Retry-After в ответ 429. Это важно для двух вещей: легитимные API-клиенты могут корректно обработать лимит, а браузеры — показать пользователю понятное сообщение.Fail2ban: автоматический бан по логам Nginx
Nginx ограничивает запросы в реальном времени, но не блокирует IP на уровне файрвола. Fail2ban читает логи и добавляет правила в iptables/nftables, полностью отрезая злоумышленников. Связка Nginx + Fail2ban — это классика, которая работает.
Первым делом настраиваем Nginx на логирование запросов, которые превысили лимит. По умолчанию он пишет в error.log с уровнем warn. Создаём отдельный фильтр для Fail2ban:
# /etc/fail2ban/filter.d/nginx-req-limit.conf
[Definition]
failregex = limiting requests, excess:.* by zone .*, client:
ignoreregex =
# /etc/fail2ban/jail.local
[nginx-req-limit]
enabled = true
filter = nginx-req-limit
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
findtime = 600
maxretry = 10
bantime = 7200
# Дополнительно — защита от брутфорса по access.log
[nginx-login-bruteforce]
enabled = true
filter = nginx-login-bruteforce
action = iptables-multiport[name=LoginBrute, port="http,https", protocol=tcp]
logpath = /var/log/nginx/access.log
findtime = 300
maxretry = 5
bantime = 86400
# /etc/fail2ban/filter.d/nginx-login-bruteforce.conf
[Definition]
failregex = ^ -.*"(GET|POST) /login.* HTTP.*" 429
ignoreregex =
Параметр bantime = 86400 — это 24 часа бана. Для брутфорса авторизации это адекватно. Для общего rate limiting я ставлю 7200 секунд (2 часа). Если атака продолжается с тех же IP после разбана — стоит переходить на постоянный бан или использовать bantime.increment = true в новых версиях Fail2ban.
Честно говоря, Fail2ban — немного устаревшее решение. На серверах с высокой нагрузкой я перехожу на nftables с динамическими наборами IP-адресов или на более современные инструменты вроде CrowdSec. Но для большинства проектов Fail2ban работает отлично и настраивается за час.
Rate limiting на уровне CDN: Cloudflare и аналоги
Если сайт стоит за CDN — и в 2026 году это должно быть у всех коммерческих проектов — то первый рубеж защиты находится именно там. Cloudflare, Fastly, AWS CloudFront, Akamai — все они предоставляют встроенные механизмы rate limiting.
В Cloudflare (Free и Pro план) rate limiting настраивается в разделе Security → WAF → Rate Limiting Rules. Я обычно настраиваю несколько правил:
- Глобальный лимит: 500 запросов за 10 секунд с одного IP — блокируем на 1 час
- Лимит для /login, /wp-login.php, /bitrix/admin/: 10 запросов за 60 секунд — блокируем на 24 часа
- Лимит для API: 100 запросов за 10 секунд — возвращаем 429 без бана
- Защита от скрейпинга: 200 запросов за 10 секунд на страницы каталога
Cloudflare работает до того, как трафик вообще дойдёт до вашего сервера. Это принципиально важно при объёмных DDoS-атаках: ваш сервер просто не видит мусорный трафик. Минус — на Free-плане возможности ограничены, нормальный rate limiting требует минимум Pro ($20/месяц).
Важный момент: если сайт стоит за Cloudflare, в Nginx нужно использовать реальный IP клиента, а не IP прокси Cloudflare. Для этого нужен модуль ngx_http_realip_module и список IP-адресов Cloudflare. Иначе rate limiting в Nginx будет работать некорректно — будет банить сами серверы Cloudflare.
# В блоке http{} nginx.conf
# Реальные IP Cloudflare (обновляется, проверяй актуальный список на cloudflare.com/ips/)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
# IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
Это критически важный момент, который часто упускают. Без этой конфигурации все ваши лимиты в Nginx будут применяться к IP-адресам серверов Cloudflare, а не к реальным клиентам. По теме настройки Nginx как прокси советую почитать отдельную статью — настройка обратного прокси Nginx.
Rate limiting для API: токены, пользователи, эндпоинты
API — отдельная история. Здесь rate limiting должен быть гранулярным: разные лимиты для разных ролей пользователей, разных эндпоинтов, разных методов HTTP. Банить по IP для API — плохая идея, потому что за одним корпоративным NAT могут сидеть сотни пользователей.
Правильная стратегия для API-rate-limiting: ограничивать по API-токену или по идентификатору пользователя, а не по IP. Анонимные запросы — по IP с жёстким лимитом. Авторизованные — по токену с более мягким лимитом, дифференцированным по тарифному плану.
В Laravel это делается через ThrottleRequests middleware с именованными лимитерами. В Laravel 10/11 это выглядит так:
user()) {
$limits = [
'free' => 100, // 100 запросов в минуту
'basic' => 500, // 500 запросов в минуту
'premium' => 2000, // 2000 запросов в минуту
];
$plan = $request->user()->subscription_plan ?? 'free';
$limit = $limits[$plan] ?? 100;
return Limit::perMinute($limit)
->by($request->user()->id)
->response(function () use ($limit) {
return response()->json([
'error' => 'Rate limit exceeded',
'message' => 'Too many requests. Please slow down.',
], 429)->withHeaders([
'X-RateLimit-Limit' => $limit,
'X-RateLimit-Remaining' => 0,
]);
});
}
// Анонимные запросы: жёсткий лимит по IP
return Limit::perMinute(30)
->by($request->ip())
->response(function () {
return response()->json([
'error' => 'Rate limit exceeded',
'message' => 'Please authenticate for higher limits.',
], 429);
});
});
// Специальный лимитер для тяжёлых эндпоинтов (поиск, экспорт)
RateLimiter::for('heavy-endpoints', function (Request $request) {
return $request->user()
? Limit::perMinute(10)->by($request->user()->id)
: Limit::perMinute(3)->by($request->ip());
});
Не забывай добавлять заголовки X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset в каждый ответ API, а не только при превышении лимита. Это хороший тон и стандарт де-факто для REST API. Клиенты смогут адаптировать свои запросы до того, как получат 429.
Мониторинг и тонкая настройка лимитов
Одна из самых частых ошибок при внедрении rate limiting — поставить слишком агрессивные лимиты и начать банить легитимных пользователей. У меня был случай: настроили лимит 10 запросов в секунду на интернет-магазине, и через день начались жалобы от пользователей с быстрым интернетом — у них страница каталога с AJAX-пагинацией генерировала 15-20 запросов при быстром скроллинге. Пришлось поднять лимит до 30 и добавить burst=50.
Перед тем как включать rate limiting в production, я делаю так: неделю пишу все лимиты в режиме "только логировать" (в Nginx это директива limit_req_log_level), смотрю реальное распределение запросов по IP, нахожу 99-й перцентиль нормального трафика и ставлю лимит на 150-200% от этого значения.
Для мониторинга количества срабатываний rate limiting в Nginx удобно использовать такой запрос к логам:
# Количество 429 ответов за последний час
awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" \
'$0 ~ d && $9 == "429" {count++} END {print count " rate-limited requests"}' \
/var/log/nginx/access.log
# Топ-20 IP по количеству заблокированных запросов
grep " 429 " /var/log/nginx/access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# Мониторинг через GoAccess в реальном времени
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
--real-time-html \
-o /var/www/html/report.html
Также советую настроить алерты в системе мониторинга. Если количество 429-ответов за минуту превысило, скажем, 1000 — это сигнал, что идёт атака или что-то сломалось у легитимных клиентов. Подробнее о настройке мониторинга в целом — в статье про мониторинг сайта.
Типичные ошибки при настройке rate limiting
За годы работы я собрал список граблей, на которые наступают снова и снова. Пройдёмся по основным.
Лимит по IP без учёта Cloudflare/прокси. Уже говорил выше, но повторю: если не настроить real_ip_header, весь трафик через CDN будет идти с одного IP. Ваш rate limiting будет банить CDN, а не атакующих.
Одинаковые лимиты для всех эндпоинтов. Статическая картинка и форма авторизации не могут иметь одинаковый rate limit. Статику вообще не нужно ограничивать rate limiting — её должен отдавать Nginx напрямую или CDN. Авторизацию — жёстко. API поиска — умеренно.
Отсутствие whitelist для мониторинга и CI/CD. Если у вас есть системы мониторинга (Zabbix, Prometheus, UptimeRobot), они регулярно стучатся на сайт. Их IP должны быть в whitelist, иначе получите ложные алерты о недоступности сайта — сам мониторинг будет заблокирован.
Не настроена страница ошибки 429. По умолчанию Nginx отдаёт стандартную страницу. Это нормально для API, но для обычных пользователей лучше настроить кастомную страницу с объяснением и таймером обратного отсчёта.
Слишком короткий bantime в Fail2ban. Бан на 10 минут — это ничто для серьёзного атакующего. Я ставлю минимум 2 часа для rate limiting и 24 часа для брутфорса авторизации. С опцией bantime.increment = true каждый следующий бан в 2 раза длиннее предыдущего.
Rate limiting без логирования. Если не логировать срабатывания, вы никогда не узнаете, была ли атака, насколько она была серьёзной и не банили ли легитимных пользователей. Логи — обязательны.
Специфика rate limiting для WordPress и Битрикс
WordPress — любимая цель атакующих именно из-за предсказуемой структуры. /wp-login.php, /xmlrpc.php, /wp-json/ — каждый знает эти пути. Для WordPress я всегда делаю три вещи помимо общего rate limiting:
Первое — блокирую /xmlrpc.php полностью, если он не нужен. Это устаревший интерфейс, который используется в 99% случаев только для атак. Второе — ставлю жёсткий лимит на /wp-login.php: 3 запроса в минуту, бан на 24 часа при превышении. Третье — ограничиваю REST API: /wp-json/ часто используется для скрейпинга контента и перебора пользователей через эндпоинт /wp-json/wp/v2/users.
Для Битрикс аналогично: /bitrix/admin/ должен быть максимально защищён — лучше вообще закрыть по IP, а публичная часть требует умеренных лимитов с учётом кеширования. Важный момент: Битрикс активно использует AJAX для обновления компонентов, так что лимит должен быть достаточно мягким для страниц с динамическим контентом. По поводу настройки кеширования в Битрикс — там тоже есть своя специфика, подробно разобрана в отдельной статье.
Если нужна помощь с настройкой защиты для конкретной CMS — это часть стандартной поддержки WordPress или поддержки Битрикс в моей практике. Rate limiting настраиваю как часть общего аудита безопасности.
Как протестировать rate limiting перед запуском
Прежде чем включать лимиты на боевом сервере, их нужно проверить. Я использую несколько инструментов: wrk, ab (Apache Benchmark) и vegeta. Последний мне нравится больше всего — гибкий, умеет задавать точную частоту запросов.
# Тест с помощью Apache Benchmark
# 1000 запросов, 50 одновременных соединений
ab -n 1000 -c 50 https://example.com/login
# С помощью wrk: 10 секунд, 4 потока, 100 соединений
wrk -t4 -c100 -d10s https://example.com/
# С помощью vegeta: точно 20 запросов в секунду в течение 30 секунд
echo "GET https://example.com/" | \
vegeta attack -rate=20/s -duration=30s | \
vegeta report
# Смотрим на статистику ответов: должны видеть 429 при превышении лимита
echo "GET https://example.com/login" | \
vegeta attack -rate=10/s -duration=60s | \
vegeta report -type=text
После теста обязательно проверяю: все ли 429 корректно логируются, сработал ли Fail2ban, не попал ли мой тестовый IP в постоянный бан (для тестов лучше использовать отдельный IP или добавить тестовый IP в whitelist Fail2ban).
И ещё один момент, который часто забывают: тестируй не только "плохой" трафик, но и то, что легитимные пользователи не попадают под ограничения. Открой сайт в браузере, быстро перейди по нескольким страницам, заполни форму — всё должно работать без ошибок 429. Если нет — лимиты слишком агрессивные.
Rate limiting — это живая конфигурация, которую нужно периодически пересматривать. Трафик растёт, паттерны атак меняются, функционал сайта расширяется. Я пересматриваю настройки лимитов раз в квартал или после любого значимого изменения в архитектуре сайта. Это не разовая настройка, а часть постоянного процесса поддержки и безопасности — как, впрочем, и регулярные бэкапы, без которых любая защита неполна.
Хотите защитить сайт от DDoS-атак и перегрузок?
Наши специалисты настроят rate limiting и комплексную защиту вашего сайта под ключ.