Привет, Хабр!
В этой статье разберём важнейший механизм защиты от перегрузок и DoS‑атак в NGINX — лимиты.
Сколько «вмещает» один воркер
Директива worker_connections
задаёт, сколько одновременных соединений готов обслужить каждый воркер‑процесс. Причём учитываются все дескрипторы — и клиентские коннекты, и прокси‑сессии к бэкендам. По дефолту NGINX идёт с worker_connections 768
, но для серьёзных нагрузок стоит поднять до 1024 или больше, не забывая про лимит открытых файлов в ОС.
Однако реальное число параллельных клиентов вычисляется так:
max_clients = worker_processes × worker_connections
Т.е при worker_processes 4; worker_connections 1024;
вы получите до 4096 одновременных соединений (минус дескрипторы для upstream).
# events.conf
events {
worker_connections 1024; # максимум соединений на воркер
# accept_mutex on; # равномерное принятие коннектов (опционально)
}
Ограничение одновременных соединений: limit_conn
Часто нужно защитить сервис от «дружелюбных» перегрузок — когда, скажем, бот краулер внезапно открывает сотни коннектов. Для этого есть ngx_http_limit_conn_module
.
limit_conn_zone — создаёт shared memory зону, где хранятся текущие соединения по ключу (обычно IP).
limit_conn — задаёт, сколько одновременных соединений разрешено.
http {
# создаём зону perip:10m, ключ — бинарный IP клиента
limit_conn_zone $binary_remote_addr zone=perip:10m;
server {
location / {
limit_conn perip 10; # не больше 10 коннектов на IP
limit_conn_status 429; # вместо дефолтного 503 выдавать 429
limit_conn_log_level info; # уровень логирования при отказе
}
}
}
Зона 10m
на 64-битной платформе способна хранить порядка 16 000 уникальных IP. При переполнении B‑tree NGINX сразу отдаёт ошибку (503/429), не пытаясь прогнать старые записи.
Dry-run и логирование
Пока не уверены в цифрах — включайте:
limit_conn_dry_run on; # не блокирует, а лишь логирует превышения
limit_conn_log_level notice;
Так вы поймёте, какие клиенты загазовываются, прежде чем реально их резать.
Ограничение скорости запросов: limit_req
Если ваша цель — не число коннектов, а частота запросов, то поможет ngx_http_limit_req_module
. Механизм основан на «leaky bucket»: запросы попадают в бакет, откуда «вытекают» с заданной скоростью.
http {
# rate=5r/s — до 5 запросов в секунду, burst=10 — пиковое «обилие»
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
server {
location /login {
limit_req zone=login burst=10 nodelay;
# nodelay — отказ без задержки, без него — плавное торможение
limit_req_status 429;
limit_req_log_level warn;
}
}
}
Без nodelay
избыточные запросы будут ждать своей очереди. С nodelay
— сразу получат отказ. Параметр delay
позволяет задерживать лишь часть запросов, отказывая только самым избыточным.
Где и как хранится состояние
В NGINX есть специальная общая память для лимитов — shared memory zones. Вы заранее выделяете кусок RAM с понятным именем и фиксированным объёмом, и в нём живут все счётчики соединений и токены для запросов. При старте мастер‑процесс создаёт там slab‑пул, т.е разбивает этот участок на одинаковые блоки, чтобы потом быстро выделять и освобождать память без лишней фрагментации.
Когда вы пишете limit_conn_zone или limit_req_zone, NGINX навешивает на эту зону свои структуры. Для limit_conn используется хеш‑таблица или сбалансированное дерево, где ключом чаще всего служит IP клиента. Для limit_req он заводит узлы с красно‑чёрным деревом для поиска нужной записи и очередью, которая моделирует протекающее ведро — сколько запросов можно пропустить и сколько отпустить с задержкой. Всё это удобно лежит в общей памяти и мгновенно доступно из любого воркера.
Каждый раз, когда приходит новый запрос или соединение, NGINX берёт ключ, хеширует его, смотрит в зону и, если записи нет, создаёт новую. После этого он проверяет счётчики: увеличивает их или рассчитывает задержку/отказ. Причём доступ к этим данным защищён встроенным мьютексом — даже при сотнях воркеров риск гонки нулевой. В итоге у вас получается механизм, который адекватно ограничивает нагрузку без каких‑либо внешних зависимостей.
Что происходит при превышении
По дефолту:
limit_conn — 503 Service Unavailable;
limit_req — 503 Service Unavailable.
Но лучше отдавать 429 Too Many Requests, чтобы клиенты понимали суть. Логи при этом выглядят так:
2025/04/04 13:45:12 [warn] 12345#0: *67890 limiting requests, excess: 5 by zone "login"
2025/04/04 13:45:12 [info] 12345#0: *67890 a client request is temporarily blocked by zone "perip"
Прочие нюансы
Размер зоны. Для 100 000 уникальных IP/сутки нужно минимум 10–12 MB. 1 MB ≈ 16 000 записей.
burst vs delay. Для login/UПИ проще выставить
burst=1; nodelay
. Для API можно плавно дросселироватьdelay 10
.Разные зоны для разных endpoint. Не зашумливайте общий лимит, а делайте под каждый путь свои zone/rate.
Исключения: при помощи
map
илиgeo
можно убирать из‑под лимита internal IP, белые списки и т. п.
map $remote_addr $limit {
10.0.0.0/8 "";
default $binary_remote_addr;
}
limit_req_zone $limit zone=api:20m rate=20r/s;
Сторонние модули и NGINX Plus-фичи
В экосистеме NGINX есть сторонние решения и расширенные возможности коммерческой версии. Рассмотрим два популярных third‑party модуля и фичи NGINX Plus.
ngx_brotli: компрессия для снижения трафика
Хотя ngx_brotli в первую очередь отвечает за Brotli‑сжатие, он так же может существенно снизить общий объём передаваемых байт и, как следствие, разгрузить систему ограничений по трафику. Установка модуля производится либо компиляцией NGINX из исходников с опцией --add-module=.../ngx_brotli
, либо через пакетный менеджер (в дистрибутивах, где есть бинарные сборки). После подключения достаточно прописать в конфиге:
http {
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript;
# …другие настройки…
}
ngx_http_limit_traffic_ratefilter_module: лимит по байтам
Если нужно не просто считать коннекты или запросы, а ограничивать скорость передачи в байтах, используйте ngx_http_limit_traffic_ratefilter_module
(часто называют ngx_http_limit_traffic_module). Он создаёт свою shared memory‑зону и позволяет, например, выставить максимум 100 KB/s на IP:
http {
limit_traffic_rate_zone $binary_remote_addr zone=bytetraf:10m;
server {
location /downloads/ {
limit_traffic_rate zone=bytetraf rate=100k;
}
}
}
Модуль компилируется в NGINX с --add-module=path/to/ngx_http_limit_traffic_ratefilter_module
и требует выделения общей памяти под счётчики.
NGINX Plus
Синхронизация зон между нодами
В кластере Open Source‑версия NGINX не умеет делиться данными лимит‑зон, поэтому каждый воркер на каждой ноде считает локально. В NGINX Plus с R15+ появилась возможность zone_sync, а в R18 её упростили до единого wildcard‑конфига. Т.е при превышении лимита на одной ноде «чёрный список» или счётчик автоматически обновляется на всех остальных, и вы получаете консистентное ограничение по всему кластеру.
Адаптивное ограничение на основе динамических метрик
Ещё одна фича NGINX Plus R19 — динамическое ограничение пропускной способности. Директивы limit_rate
и limit_rate_after
теперь принимают переменные, позволяя менять скорость передачи по запросу в зависимости от метрик. Пример:
map $upstream_response_time $dyn_rate {
"~^[0-9]\.[0-1]" 200k; # если быстрый ответ, даём больше скорости
default 50k; # при замедлении — жёстко дросселируем
}
server {
location /stream/ {
limit_rate $dyn_rate;
}
}
С помощью это можно реализовать адаптивность под текущую нагрузку и давая лишний спурт только тем, у кого он действительно оправдан.
Если вы работаете с NGINX или стремитесь углубить свои знания в области масштабируемости и производительности систем, открытые уроки могут стать полезным дополнением к вашему опыту:
22 мая 19:00
Оптимизация Nginx и Angie под высокие нагрузки29 мая 19:00
Маршрутизатор на базе Linux: настройка и запуск10 июня 20:00
Управление потоками ввода и вывода в Linux
Список всех открытых уроков по разным ИТ-направлениям смотрите в календаре.
x1shn1k
Хочу сказать спасибо за статью!
Я nginx использую в основном как реверс-прокси, но с удоволствием читаю вашу серию статей по nginx, и всегда что-то новое для себя нахожу.