Настройка 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;

или скорректируйте лимит, если он срабатывает на нормальный трафик.

Безопасный порядок настройки

  1. Определить защищаемый endpoint.
  2. Измерить нормальную частоту запросов.
  3. Создать резервную копию.
  4. Создать limit_req_zone.
  5. Применить limit_req только к нужному location.
  6. Задать burst.
  7. Установить код 429.
  8. Выполнить nginx -t.
  9. Выполнить reload.
  10. Провести умеренный тест.
  11. Проверить логи и нормальных пользователей.
  12. Скорректировать 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 должен применяться точечно и опираться на фактический профиль нормального трафика.

← Предыдущая статья Ограничение размера загружаемых файлов в Nginx Следующая статья → Перенаправление с www на основной домен в Nginx