Ограничение скорости в Nginx: типичные ошибки
Nginx может защитить API от всплеска запросов, но неправильные burst, ключ лимита и доверие к IP превращают защиту в источник 503 для обычных пользователей. Разбираем настройки, которые чаще всего ломаются.

Один неверный параметр в limit_req способен либо пропустить всплеск запросов к API, либо начать отдавать клиентам 503 уже при нескольких параллельных обращениях. В Nginx 1.25–1.27 ограничение скорости строится из зоны общей памяти, ключа группировки и очереди burst; ниже — рабочая схема и ошибки, из-за которых она ведёт себя не так, как ожидается.
Как устроено ограничение запросов
Модуль ngx_http_limit_req_module использует алгоритм «ведро с утечкой»: Nginx считает запросы для каждого ключа и поддерживает заданную среднюю скорость. Ключом чаще всего служит IP-адрес клиента, но им может быть и другой набор данных.
Директива limit_req_zone создаёт зону в общей памяти. Она должна находиться в контексте http. Директива limit_req подключает эту зону к конкретному серверу или location.
http {
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=5r/s;
server {
listen 80;
server_name example.com;
location /api/ {
limit_req zone=api_per_ip burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
В примере один IP получает среднюю скорость пять запросов в секунду. Ещё до 20 запросов могут попасть в очередь burst. Параметр nodelay пропускает запросы из этой очереди сразу, не растягивая их во времени. Без него Nginx будет задерживать часть запросов, чтобы выдержать установленную скорость.
* Burst — запас запросов сверх базовой скорости. Это не дополнительная постоянная квота, а очередь для короткого всплеска.
Лимит — это не только число запросов в секунду. Нужно одновременно выбрать ключ, размер очереди и реакцию на превышение.
Пять ошибок, которые встречаются чаще всего
1. Зона объявлена не в том контексте
limit_req_zone нельзя размещать внутри server или location. При проверке конфигурации Nginx сообщит об ошибке вроде "limit_req_zone" directive is not allowed here.
Исправление — перенести объявление в секцию http, а в location оставить только подключение зоны:
http {
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=5r/s;
server {
location /api/ {
limit_req zone=api_per_ip burst=20 nodelay;
}
}
}
После изменения сначала проверьте конфигурацию командой nginx -t, затем перезагрузите Nginx штатным для вашей системы способом. Проверка успешна, если команда завершается сообщением syntax is ok и test is successful.
2. Лимит считается не по тому IP
На сервере за CDN, балансировщиком или обратным прокси переменная $remote_addr часто содержит адрес прокси, а не посетителя. Тогда все клиенты делят одну квоту и получают 429 или 503 одновременно.
Просто заменить ключ на $http_x_forwarded_for опасно: клиент может самостоятельно прислать такой заголовок и подставить новый адрес на каждый запрос. Сначала настройте доверенную обработку адресов через модуль realip и только после этого используйте результат этой обработки в лимите. Список доверенных сетей зависит от вашего CDN или балансировщика, поэтому его нельзя копировать из случайного примера.
http {
# Сети здесь приведены только как пример. Укажите реальные адреса
# доверенного прокси из его официальной документации.
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=5r/s;
}
После настройки проверьте access log: в нём должен записываться реальный адрес клиента, а не общий адрес прокси. Если у цепочки прокси другая схема заголовков, параметры realip нужно сверить с документацией инфраструктуры.
3. Burst принимают за вторую скорость
Настройка rate=5r/s burst=100 не означает, что клиенту разрешено 105 запросов в секунду. Она разрешает среднюю скорость пять запросов в секунду и временный запас до 100 запросов. При большом burst запросы либо ждут, либо проходят немедленно с nodelay.
| Настройка | Поведение | Цена ошибки |
|---|---|---|
rate=5r/s | Средняя скорость для одного ключа | Слишком низкое значение режет нормальный трафик |
burst=20 | Очередь короткого всплеска | Слишком маленькая очередь даёт отказы при пиках |
nodelay | Очередные запросы проходят без искусственной задержки | Backend получает резкий всплеск |
Без nodelay | Запросы из burst обслуживаются с задержкой | Клиент может получить таймаут при длинной очереди |
Для интерактивного API обычно важнее небольшая задержка, чем попытка принять длинную очередь: используют умеренный burst и проверяют нагрузку на backend. Для фоновых запросов задержка может быть приемлемой. Значения нужно подбирать по реальному профилю endpoint, а не переносить с главной страницы на операцию импорта.
4. Ограничение включают на весь сайт
Когда limit_req ставят в корневой location, под один лимит попадают HTML, CSS, изображения, health check и API. Страница с десятками ресурсов быстро расходует тот же бюджет, что и отдельный пользовательский запрос к backend.
Разделяйте зоны по назначению. Для публичного API нужен один лимит, для входа — более строгий, для статических файлов ограничение обычно не требуется на уровне Nginx. Не смешивайте в одной зоне запросы с разной стоимостью: лёгкий GET и тяжёлый POST к отчёту создают одинаковый счётчик.
http {
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=login_per_ip:10m rate=1r/s;
server {
location /api/ {
limit_req zone=api_per_ip burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location = /login {
limit_req zone=login_per_ip burst=5;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
Здесь точное совпадение location = /login не затрагивает остальные страницы. После проверки отправьте несколько запросов именно к нужному endpoint и убедитесь, что статические файлы не попали под тот же лимит.
5. Отказ маскируют под ошибку сервера
По умолчанию превышение лимита связано с ответом 503. Для клиента это выглядит как временная поломка сервера, а не как ограничение частоты. Для API чаще понятнее использовать 429, если это согласуется с контрактом backend и клиента.
location /api/ {
limit_req zone=api_per_ip burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
Не меняйте код ответа вслепую: проверьте, как клиент обрабатывает 429, повторяет ли запрос и есть ли у него ограничение числа повторов. Иначе rate limit может запустить лавину ретраев.
Как проверять настройку до продакшена
Сначала включите режим наблюдения. Директива limit_req_dry_run on учитывает превышения и пишет их в лог, но не отклоняет запросы. Это помогает увидеть, насколько часто выбранный лимит заденет реальных пользователей.
location /api/ {
limit_req zone=api_per_ip burst=20 nodelay;
limit_req_dry_run on;
}
Временно включите расширенный уровень логирования для модуля, затем сравните количество превышений с latency и ошибками backend. В документации Nginx уровни журнала для limit_req задаются через limit_req_log_level; не оставляйте подробное логирование постоянно без оценки объёма логов.
- Проверьте конфигурацию командой
nginx -t. - Сделайте запросы с одного IP и убедитесь, что превышение фиксируется.
- Повторите тест с разных адресов: ключ не должен случайно объединять всех клиентов.
- Проверьте ответ API: ожидается выбранный код 429, а не случайный 502 от прокси.
- Снимите метрики backend: ограничение не должно просто переносить всплеск в очередь приложения.
Где схема ломается
Ограничение по IP плохо подходит для сетей, где тысячи пользователей выходят через один NAT: офис, мобильный оператор или публичный Wi‑Fi. Обратная крайность — IPv6, где особенности адресации могут сделать группировку слишком мелкой или нестабильной для вашей модели доступа.
Общая зона ограничивает запросы между worker-процессами Nginx, но не превращает несколько независимых серверов в единый счётчик. Если трафик распределён между несколькими узлами, каждый узел может иметь собственную зону. Для единого глобального лимита нужен отдельный координирующий слой, например механизм на уровне API-шлюза или приложения.
Не используйте rate limit как замену аутентификации, WAF или контролю очередей. Он ограничивает частоту запросов, но не решает проблему дорогих операций, больших тел запросов и злоупотребления уже авторизованными аккаунтами.
Что делать дальше
Начните с dry run на одном API endpoint. Зафиксируйте число запросов, p95 latency, долю ответов 429 и нагрузку backend за период с обычным трафиком. Затем отдельно настройте лимиты для анонимных IP, авторизованных клиентов и тяжёлых операций. После включения блокировки оставьте алерты на рост 429, 5xx и повторных запросов: по этим трём сигналам видно, защищает ли настройка систему или отсекает легитимных пользователей.
Поиск источников: Реальные статьи и книги со ссылками, библиография по ГОСТ
Спросить за 15 ₽Источники: Официальная документация Nginx: ngx_http_limit_req_module, Официальная документация Nginx: ngx_http_realip_module
Частые вопросы
Почему Nginx возвращает 503 вместо 429?
Почему все пользователи получают отказ одновременно?
Можно ли считать запросы по токену или пользователю, а не по IP?
Что будет, если я поставлю слишком маленький burst?
Достаточно ли limit_req для защиты API?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.