Настройка rate limiting в Nginx
Пошаговая настройка ограничения частоты запросов в Nginx с помощью limit_req_zone и limit_req для сайта, API и формы авторизации.
Rate limiting ограничивает количество запросов от одного клиента за заданный интервал времени. Это помогает снизить нагрузку на backend и затрудняет автоматический перебор, агрессивный парсинг и простые HTTP-flood атаки.
В этой инструкции рассматривается только настройка ограничения частоты запросов в Nginx с помощью директив limit_req_zone и limit_req.
Проверено на: Ubuntu Server 24.04 LTS
Уровень сложности: средний
Время выполнения: около 15–20 минут
Требуемый доступ: пользователь с правамиsudo
Что будет настроено
После выполнения инструкции можно будет:
- создать общую зону учёта запросов;
- ограничить частоту запросов для сайта;
- настроить отдельный лимит для API;
- настроить более строгий лимит для формы входа;
- использовать параметры
burstиnodelay; - проверить ответ
429 Too Many Requests; - исключить влияние доверенного reverse proxy;
- безопасно применить и отменить конфигурацию.
Как работает rate limiting
Nginx хранит состояние клиентов в общей memory zone.
Ключ обычно строится по IP-адресу:
$binary_remote_addr
Для каждого ключа Nginx считает частоту запросов.
Если клиент превышает установленный лимит, запрос:
- задерживается;
- либо отклоняется;
- либо пропускается в пределах
burst.
Основные директивы
Для настройки используются:
limit_req_zone
limit_req
limit_req_status
limit_req_log_level
limit_req_zone
Создаёт общую зону и задаёт базовую скорость.
limit_req
Применяет созданную зону в server или location.
limit_req_status
Задаёт HTTP-код при отклонении запроса.
limit_req_log_level
Задаёт уровень журналирования ограниченных запросов.
Где размещать limit_req_zone
Директива:
limit_req_zone
разрешена только в контексте:
http
Обычно её добавляют в:
/etc/nginx/nginx.conf
внутри блока:
http {
}
Создание резервной копии
Сохраните основной конфигурационный файл:
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.before-rate-limit
Сохраните server block:
sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.before-rate-limit
Создание общей зоны
Откройте:
sudo nano /etc/nginx/nginx.conf
Внутри блока http добавьте:
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
Разбор параметров зоны
$binary_remote_addr
Использует бинарное представление IP и экономит память.
zone=per_ip:10m
Создаёт shared memory zone размером 10 МБ.
rate=10r/s
Разрешает в среднем 10 запросов в секунду с одного IP.
Скорость в запросах в минуту
Можно использовать:
rate=30r/m;
Это означает 30 запросов в минуту.
Такой формат удобен для:
- формы входа;
- password reset;
- отправки кода подтверждения;
- создания обращений;
- тяжёлых API-операций.
Применение лимита ко всему сайту
Откройте server block:
sudo nano /etc/nginx/sites-available/example.com
Добавьте:
server {
server_name example.com;
limit_req zone=per_ip burst=20 nodelay;
location / {
try_files $uri $uri/ =404;
}
}
Параметр burst
burst=20
Разрешает кратковременный всплеск до 20 лишних запросов сверх базовой скорости.
Без burst даже короткая серия параллельных запросов может быть отклонена.
Параметр nodelay
nodelay
не задерживает запросы из burst-очереди.
Они проходят сразу, но учитываются в лимите.
Без nodelay Nginx будет постепенно выпускать burst-запросы с заданной скоростью.
Вариант без nodelay
limit_req zone=per_ip burst=20;
Такой вариант сглаживает всплески, но может увеличить задержку ответа.
Он подходит для API, где постепенная обработка приемлема.
HTTP-код при превышении лимита
По умолчанию Nginx может возвращать код, отличный от привычного 429.
Задайте явно:
limit_req_status 429;
Добавьте в server:
server {
limit_req_status 429;
}
Уровень журналирования
limit_req_log_level warn;
Добавьте в server:
limit_req_log_level warn;
Отклонённые запросы будут записываться с уровнем warn.
Не используйте слишком подробное логирование на высоконагруженном публичном endpoint без необходимости.
Отдельная зона для API
В http создайте:
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=5r/s;
Примените:
location /api/ {
limit_req zone=api_per_ip burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Отдельная зона для формы входа
В http:
limit_req_zone $binary_remote_addr zone=login_per_ip:10m rate=5r/m;
В server block:
location = /login {
limit_req zone=login_per_ip burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Это ограничивает один IP примерно пятью запросами в минуту с небольшим burst.
Точное совпадение location
location = /login
применяется только к точному пути:
/login
Он не затрагивает:
/login/help
/login/reset
Ограничение password reset
location = /password-reset {
limit_req zone=login_per_ip burst=2 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Отдельные лимиты для разных endpoint
Пример:
location /api/public/ {
limit_req zone=api_per_ip burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /api/admin/ {
limit_req zone=login_per_ip burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Не применяйте одинаковый лимит ко всем endpoint без оценки их назначения.
Пример итоговой конфигурации зон
В /etc/nginx/nginx.conf:
http {
limit_req_zone $binary_remote_addr zone=site_per_ip:10m rate=10r/s;
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=5r/m;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Пример итогового server block
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
limit_req_status 429;
limit_req_log_level warn;
location / {
limit_req zone=site_per_ip burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /api/ {
limit_req zone=api_per_ip burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location = /login {
limit_req zone=login_per_ip burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
Проверка синтаксиса
sudo nginx -t
Ожидаемый результат:
syntax is ok
test is successful
Применение
sudo systemctl reload nginx
Проверьте:
systemctl status nginx --no-pager
Проверка обычного запроса
curl -I https://example.com
Ожидается обычный успешный ответ.
Нагрузочная проверка через цикл
for i in $(seq 1 50); do
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/login
done
Часть запросов должна получить:
429
Проверка через xargs
seq 1 50 |
xargs -n1 -P20 curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/login
Параметр:
-P20
создаёт до 20 параллельных запросов.
Не запускайте интенсивные тесты против production-сайта без согласования.
Проверка через ApacheBench
Установите:
sudo apt update
sudo apt install -y apache2-utils
Запустите:
ab -n 100 -c 20 https://example.com/login
Где:
-n 100 — всего 100 запросов
-c 20 — 20 параллельных запросов
Проверка через hey
Если hey установлен:
hey -n 100 -c 20 https://example.com/login
Не устанавливайте дополнительные инструменты без необходимости.
Проверка кодов ответа
for i in $(seq 1 30); do
curl -sS -o /dev/null -w '%{http_code} ' https://example.com/login
done
echo
Ожидается смесь:
200 200 429 429 ...
Конкретный результат зависит от backend, скорости и burst.
Проверка журнала ошибок
sudo tail -100 /var/log/nginx/example.com.error.log
Поиск ограниченных запросов:
sudo grep -i 'limiting requests' /var/log/nginx/example.com.error.log |
tail -20
Типичная запись:
limiting requests, excess: ...
Пользовательская страница 429
Создайте:
sudo mkdir -p /var/www/example.com/errors
sudo tee /var/www/example.com/errors/429.html >/dev/null <<'EOF'
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Слишком много запросов</title>
</head>
<body>
<h1>Слишком много запросов</h1>
<p>Повторите попытку позже.</p>
</body>
</html>
EOF
Добавьте:
error_page 429 /429.html;
location = /429.html {
root /var/www/example.com/errors;
internal;
}
Заголовок Retry-After
Nginx не всегда добавляет Retry-After автоматически.
Можно добавить:
add_header Retry-After "60" always;
Но если разместить его на уровне всего server block, он появится и в обычных ответах.
Для точной обработки требуется отдельная named location или обработка на уровне приложения.
Не добавляйте Retry-After глобально без необходимости.
Rate limiting и reverse proxy
Если перед Nginx находится CDN, WAF или load balancer, переменная:
$binary_remote_addr
может содержать IP внешнего proxy.
Тогда все клиенты будут учитываться как один адрес.
Настройка real IP
Для доверенного proxy:
set_real_ip_from PROXY_SUBNET;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Пример:
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
После этого $binary_remote_addr будет использовать восстановленный IP клиента.
Нельзя доверять всем адресам
Опасный вариант:
set_real_ip_from 0.0.0.0/0;
Клиент сможет подставить произвольный X-Forwarded-For и обходить лимит.
Указывайте только доверенные адреса proxy.
Проверка IP, который видит Nginx
Проверьте access log:
sudo tail -20 /var/log/nginx/example.com.access.log
При необходимости добавьте временный log format с $remote_addr и $http_x_forwarded_for.
Ограничение по API key вместо IP
Стандартный limit_req_zone может использовать другой ключ.
Пример:
limit_req_zone $http_x_api_key zone=api_key_limit:10m rate=10r/s;
Но пустой или поддельный header создаёт дополнительные риски.
Такой вариант применяйте только при контролируемой API-аутентификации.
Комбинированный ключ
Можно использовать комбинацию:
$binary_remote_addr$uri
Пример:
limit_req_zone $binary_remote_addr$uri zone=per_ip_uri:20m rate=5r/s;
Это создаёт отдельный счётчик для каждого IP и URI.
Память будет расходоваться быстрее.
Размер memory zone
Пример:
zone=per_ip:10m
10 МБ обычно достаточно для большого количества активных IP-ключей.
Если зона переполнится, новые записи не смогут учитываться корректно.
Не увеличивайте размер без оценки нагрузки.
Несколько worker processes
Shared memory zone используется всеми worker processes.
Лимит действует на весь экземпляр Nginx, а не отдельно на каждый worker.
Rate limiting и несколько Nginx-серверов
Если трафик распределён между несколькими независимыми Nginx:
Load Balancer → Nginx 1
→ Nginx 2
каждый сервер считает лимит отдельно.
Общий кластерный лимит требует внешнего rate limiter или поддержки на уровне API gateway.
Rate limiting не заменяет защиту приложения
Ограничение Nginx не заменяет:
- блокировку учётной записи;
- CAPTCHA;
- MFA;
- application-level throttling;
- WAF;
- Fail2ban;
- защиту API key;
- аудит входов.
Для формы авторизации лимит должен существовать и в приложении.
Не ограничивайте healthcheck без необходимости
Healthcheck может выполняться часто и с одного IP.
Не добавляйте строгий лимит на:
/health
/ready
/metrics
без учёта мониторинга.
Не ограничивайте статические файлы тем же лимитом
Одна HTML-страница может вызвать десятки запросов:
- CSS;
- JavaScript;
- изображения;
- шрифты;
- favicon.
Слишком низкий глобальный лимит приведёт к случайным 429.
Для публичного сайта лучше ограничивать:
- API;
- login;
- search;
- password reset;
- тяжёлые endpoint.
Лимит для поиска
location = /search {
limit_req zone=api_per_ip burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Это снижает нагрузку от автоматического перебора поисковых запросов.
Лимит для webhook
Для webhook лимит нужно согласовать с отправителем.
Слишком строгая настройка может привести к потере событий.
Проверяйте retry-механику внешнего сервиса.
Исключение доверенной подсети
Для исключения trusted network можно использовать map.
В http:
geo $limit_key {
default $binary_remote_addr;
192.0.2.0/24 "";
}
Зона:
limit_req_zone $limit_key zone=per_ip:10m rate=10r/s;
Пустой ключ не учитывается.
Используйте исключения только для действительно доверенных сетей.
Проверка исключения
С доверенного IP создайте серию запросов.
Убедитесь, что 429 не появляется.
С внешнего IP ограничение должно работать.
Отключение rate limiting
Удалите или закомментируйте:
limit_req ...
Зоны limit_req_zone можно оставить, но неиспользуемую конфигурацию лучше удалить после проверки.
Выполните:
sudo nginx -t &&
sudo systemctl reload nginx
Восстановление резервной копии
sudo cp /etc/nginx/nginx.conf.before-rate-limit /etc/nginx/nginx.conf
sudo cp /etc/nginx/sites-available/example.com.before-rate-limit /etc/nginx/sites-available/example.com
Проверьте:
sudo nginx -t
Примените:
sudo systemctl reload nginx
Типичные проблемы
Все пользователи получают 429
Причины:
- слишком низкий rate;
- слишком маленький burst;
- Nginx видит IP reverse proxy;
- лимит применён ко всему сайту;
- frontend создаёт много параллельных запросов.
Проверьте access log и real IP.
Ограничение не работает
Проверьте:
- используется ли зона;
- активен ли нужный server block;
- совпадает ли location;
- выполнен ли reload;
- нет ли bypass route;
- итоговую конфигурацию.
Команда:
sudo nginx -T |
grep -nE 'limit_req_zone|limit_req '
nginx -t сообщает zero size shared memory zone
Проверьте синтаксис:
zone=per_ip:10m
Размер memory zone обязателен.
unknown limit_req_zone
Директива limit_req ссылается на зону, которая не создана в http.
Проверьте имя зоны.
Один клиент обходит лимит через IPv6
IPv4 и IPv6 имеют разные адреса и отдельные ключи.
Это нормальное поведение.
Для строгой идентификации используйте application account или API key.
Мониторинг получает 429
Исключите его IP или увеличьте лимит только для healthcheck endpoint.
Логи быстро растут
Уменьшите уровень:
limit_req_log_level error;
или скорректируйте лимит, если он срабатывает на нормальный трафик.
Безопасный порядок настройки
- Определить защищаемый endpoint.
- Измерить нормальную частоту запросов.
- Создать резервную копию.
- Создать
limit_req_zone. - Применить
limit_reqтолько к нужному location. - Задать
burst. - Установить код
429. - Выполнить
nginx -t. - Выполнить reload.
- Провести умеренный тест.
- Проверить логи и нормальных пользователей.
- Скорректировать rate и burst.
Быстрый пример
В http:
limit_req_zone $binary_remote_addr zone=login_per_ip:10m rate=5r/m;
В server:
limit_req_status 429;
location = /login {
limit_req zone=login_per_ip burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Быстрая проверка
sudo nginx -t &&
sudo systemctl reload nginx
for i in $(seq 1 20); do
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/login
done
Итог
После выполнения инструкции:
- создана shared memory zone;
- настроена базовая скорость запросов;
- добавлены
burstиnodelay; - настроены отдельные лимиты для API и login;
- установлен ответ
429; - проверено поведение через
curl; - учтён reverse proxy и real IP;
- разобраны типичные ошибки.
Rate limiting должен применяться точечно и опираться на фактический профиль нормального трафика.