Rate limiting в Nginx: limit_req и limit_conn для защиты от перебора и всплесков трафика

Модули ngx_http_limit_req_module и ngx_http_limit_conn_module встроены в Nginx по умолчанию и не требуют дополнительных пакетов. Они решают две разные задачи: limit_req ограничивает частоту запросов от одного клиента, limit_conn — количество одновременных соединений. Вместе они закрывают основные сценарии: перебор паролей, агрессивный парсинг, всплески трафика от ботов и исчерпание пула соединений к backend.

Как работает leaky bucket​


Nginx реализует алгоритм «дырявого ведра» (leaky bucket). Каждый запрос от клиента добавляет единицу в виртуальное ведро, которое опустошается с фиксированной скоростью. Если ведро переполнено — запрос отклоняется.

Конкретнее: при rate 10r/s Nginx пропускает один запрос каждые 100 мс. Если запросы приходят чаще, они либо ставятся в очередь (при наличии burst), либо сразу получают отказ. Очередь тоже ограничена — её размер задаётся параметром burst.

limit_req_zone: определение зоны​


Директива limit_req_zone объявляет область в разделяемой памяти, где хранятся счётчики. Размещается в контексте http.

NGINX:
http {
    limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
}

Параметры:

ПараметрНазначение
$binary_remote_addrКлюч, по которому ведётся подсчёт. Бинарное представление IP занимает 4 байта (IPv4) или 16 байт (IPv6), что экономит память по сравнению с $remote_addr.
zone=req_per_ip:10mИмя зоны и размер разделяемой памяти. 1 МБ хранит примерно 16 000 состояний.
rate=10r/sСкорость «утечки». Допустимы r/s (запросы в секунду) и r/m (запросы в минуту).

В качестве ключа можно использовать не только IP. Например, $server_name для ограничения на весь виртуальный хост, $http_x_api_key для ограничения по API-ключу или комбинацию через конкатенацию.

limit_req: применение к location​


Директива limit_req подключает зону к конкретному контексту (server, location, limit_except).

NGINX:
location /api/ {
    limit_req zone=req_per_ip burst=20 nodelay;
    proxy_pass http://backend;
}

burst и nodelay​


  • burst=20 — размер очереди. Если запросы приходят быстрее rate, до 20 лишних запросов будут поставлены в очередь и обработаны позже, с задержкой.
  • nodelay — запросы из очереди обрабатываются сразу, без искусственной задержки. Без nodelay клиент получает ответы с интервалом, соответствующим rate, даже если backend способен ответить быстрее.

Типичная комбинация для API: rate=10r/s burst=20 nodelay. Это означает: устойчивый поток — 10 запросов в секунду, кратковременный всплеск до 30 запросов (10 + 20 из burst) проходит без задержки, всё сверх — отклоняется.

Без burst любой запрос сверх rate немедленно получает ошибку. Без nodelay burst-запросы обрабатываются по одному каждые 100 мс (при rate=10r/s), что увеличивает latency для клиента.

limit_conn_zone и limit_conn​


Модуль limit_conn ограничивает число одновременных соединений, а не частоту запросов. Это полезно, когда один клиент открывает много параллельных соединений и исчерпывает worker-ресурсы.

NGINX:
http {
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
}

server {
    location /downloads/ {
        limit_conn conn_per_ip 5;
        ## ...
    }
}

Здесь клиенту разрешено не более 5 одновременных соединений к /downloads/. Шестое и последующие получат отказ.

limit_conn можно применять несколько раз в одном location с разными зонами — например, одновременно ограничить по IP и по $server_name.

Коды ответа и логирование​


По умолчанию при срабатывании ограничения Nginx возвращает 503 Service Temporarily Unavailable. Это не всегда корректно с точки зрения семантики HTTP. Для перебора паролей логичнее вернуть 429 Too Many Requests.

NGINX:
limit_req_status 429;
limit_conn_status 429;

Уровень логирования по умолчанию — error. Если лимиты срабатывают часто и засоряют error-лог, можно понизить:

NGINX:
limit_req_log_level warn;
limit_conn_log_level warn;

При limit_req_log_level info события попадают только в access-лог, что удобно для мониторинга без алертов.

Практические сценарии​


Защита формы логина от перебора​


NGINX:
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

server {
    location /login {
        limit_req zone=login burst=5 nodelay;
        limit_req_status 429;
        proxy_pass http://app;
    }
}

Один запрос в секунду на IP, всплеск до 6 попыток (1 + burst 5) проходит без задержки. Седьмая попытка в ту же секунду — 429. Для брутфорса с перебором словаря это серьёзное замедление.

Ограничение API с дифференциацией по ключу​


NGINX:
map $http_x_api_key $api_limit_key {
    default $binary_remote_addr;
    ~.+   $http_x_api_key;
}

limit_req_zone $api_limit_key zone=api:20m rate=30r/s;

location /api/v1/ {
    limit_req zone=api burst=50 nodelay;
    proxy_pass http://api_backend;
}

Если заголовок X-API-Key присутствует — лимит считается по ключу. Если нет — по IP. Это позволяет авторизованным клиентам получать более высокий лимит, а анонимный трафик ограничивать по адресу.

Ограничение одновременных загрузок​


NGINX:
limit_conn_zone $binary_remote_addr zone=dl:10m;

location /files/ {
    limit_conn dl 2;
    limit_rate 1m;  # дополнительно: ограничение скорости отдачи
}

Не более двух параллельных загрузок на IP, каждая не быстрее 1 МБ/с.

Типичные ошибки​


Зона объявлена, но не подключена. limit_req_zone без соответствующей limit_req в location не делает ничего. Это самая частая причина «не работает».

Слишком маленький размер зоны. Если зона заполняется, Nginx не может создать новое состояние для нового ключа и возвращает 503 всем. При большом количестве уникальных IP увеличивайте размер: zone=req_per_ip:50m.

burst=0 (по умолчанию) без понимания последствий. Без burst любой запрос сверх rate отклоняется мгновенно. Для страниц с параллельной загрузкой ресурсов (CSS, JS, изображения) это приводит к массовым 503 для легитимных пользователей. Ставьте burst хотя бы 10–20 для обычных страниц.

Ограничение по $remote_addr вместо $binary_remote_addr. Строковое представление IP занимает больше памяти. При 10 МБ зоны разница в количестве хранимых состояний — примерно в 2–4 раза.

Забытый limit_req_status. Клиенты и мониторинг видят 503, который обычно означает проблему с backend, а не rate limit. Это усложняет диагностику.

Конфликт с proxy_cache_lock или limit_conn на уровне upstream. Если backend тоже ограничивает соединения, двойное ограничение может дать непредсказуемое поведение. Проверяйте суммарные лимиты.

Проверка результата​


После применения конфигурации:

  1. Синтаксис: nginx -t — убедитесь, что нет ошибок парсинга.
  2. Перезагрузка: nginx -s reload или systemctl reload nginx.
  3. Функциональный тест: отправьте серию запросов быстрее rate и убедитесь, что лишние получают 429 (или 503, если статус не переопределён).

Bash:
for i in $(seq 1 20); do
  curl -s -o /dev/null -w "%{http_code}\n" http://example.com/login
done

При rate=1r/s burst=5 nodelay первые 6 запросов вернут 200, остальные — 429.

  • Логи: проверьте error-лог на наличие строк limiting requests или limiting connections. Если их нет при нагрузке — зона не подключена или rate слишком высокий.
  • Мониторинг в продакшене: добавьте в access-лог переменную $limit_req_status (доступна с Nginx 1.17.1), чтобы видеть, какие запросы были ограничены, без парсинга error-лога.

NGINX:
log_format rate '$remote_addr [$time_local] "$request" $status '
                'limit_req=$limit_req_status limit_conn=$limit_conn_status';
access_log /var/log/nginx/rate.log rate;

Ограничения модулей​


  • limit_req и limit_conn работают на уровне одного процесса Nginx. В кластере из нескольких серверов за балансировщиком каждый инстанс считает независимо. Для глобального лимита нужна внешняя система (например, Redis + Lua через ngx_http_lua_module или Nginx Plus с zone sync).
  • Модули не различают «хороших» и «плохих» ботов. Если нужно пропустить поисковых crawlers без ограничений, добавьте geo или map с белым списком и используйте переменную-ключ, которая для них возвращает пустую строку (пустой ключ отключает подсчёт).
  • limit_conn считает соединения на этапе accept, до обработки заголовков. Это значит, что медленные соединения (slowloris) тоже занимают слот.

Для более сложных сценариев — адаптивные лимиты, приоритизация по типу клиента, интеграция с WAF — смотрите в сторону Nginx как reverse proxy: проксирование запросов к backend-сервисам с правильной передачей заголовков Nginx как reverse proxy с передачей заголовков и связки с внешними сервисами.

Источники​


 
Назад
Верх Низ