Техническая глубина
Укрепление nginx против DDoS: лимиты соединений, зоны скорости и таймауты
Обновлено: август 2026 г. · Зоны скорости, лимиты соединений и таймауты по директивам · Время чтения ~19 мин

nginx устойчив к атакам медленными соединениями по своей архитектуре, потому что цикл событий не тратит поток на соединение. Но против потока запросов он не делает ничего, пока не настроен. Несущие директивы — это limit_req_zone, limit_conn_zone и четыре таймаута; они ключуются по $binary_remote_addr, масштабируются по вашим базовым показателям и проверяются через limit_req_dry_run прежде, чем отбросят хоть одного реального пользователя.
Это руководство укрепляет сам nginx против той части DDoS-атаки, что исчерпывает запросы и соединения, без устройства перед ним. nginx стартует с архитектурного преимущества: цикл событий делает его куда устойчивее к атакам медленными соединениями, чем сервер с потоком на соединение. Но это преимущество покрывает ровно одну форму атаки. Против потока запросов nginx «из коробки» не делает ничего, пока вы не зададите ему лимиты.
Каждая директива ниже приводится со своим значением, счётчиком, который показывает её работу, и там, где уместно, пробным прогоном. Значения — это отправные точки, измеренные по вашему трафику. Скорость, скопированная из руководства, либо бесполезна, либо режет ваших же пользователей.
| Устройство | Форма атаки | Директива nginx | Проверка |
|---|---|---|---|
| Поток запросов с немногих источников | limit_req_zone + limit_req (burst, nodelay) | доля 429 в access log; сначала limit_req_dry_run | |
| Много одновременных соединений с источника | limit_conn_zone + limit_conn | limit_conn_status 429; $connections в stub_status | |
| Медленный заголовок / тело (Slowloris) | client_header_timeout, client_body_timeout | reset_timedout_connection; error log на уровне info | |
| Злоупотребление большими заголовками / телом | large_client_header_buffers, client_max_body_size | доля 413 / 400 в access log | |
| Исчерпание дескрипторов / воркеров | worker_connections, worker_rlimit_nofile | active в stub_status против потолка worker_connections |
Каждая строка задаётся в nginx.conf и проверяется по access log или stub_status. Ничто из этого не помогает, когда атака заполняет канал перед nginx, — это задача сетевого уровня.
0. Сначала базовые показатели: включите числа, по которым будете настраивать
Нельзя масштабировать ограничение, которое вы не измерили. Включите stub_status и убедитесь, что
access log несёт время запроса и статус лимита:
# In an internal-only server block
location = /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
# A log format that shows rate/conn limiting and timing
log_format ddos '$remote_addr $status $request_time '
'$limit_req_status $limit_conn_status "$request"';
access_log /var/log/nginx/access.log ddos;
# Live connection state
curl -s http://127.0.0.1/nginx_status
# Active connections, reading/writing/waiting; compare 'Active' to worker_connections
# Requests per second per client, from the log, over a normal week
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Отношение числа Active к потолку worker_connections и частота запросов на клиента из лога — это
два базовых показателя, относительно которых выбирается каждый порог ниже.
1. Ёмкость воркеров: потолок, под которым лежит всё остальное
Прежде любого правила скорости nginx должно быть разрешено достаточно соединений и дескрипторов, иначе он исчерпает себя раньше, чем сработает лимит.
worker_processes auto; # one per core
worker_rlimit_nofile 262144; # must be >= worker_connections * 2
events {
worker_connections 32768; # per worker; total = this * worker_processes
multi_accept on;
use epoll; # Linux; the event loop that makes nginx scale
}
worker_rlimit_nofile должен превышать worker_connections с запасом на сокеты вышестоящих
соединений и файлы, а сервисный лимит ОС, в свою очередь, должен превышать его — LimitNOFILE в
unit-файле systemd, как описано в руководстве по укреплению Linux.
Если лимит ОС ниже, именно он — реальный потолок, который никакая директива nginx не поднимет.
# Confirm nginx actually got the descriptors
cat /proc/$(pgrep -o -x nginx)/limits | grep 'open files'
2. Ограничение скорости: limit_req, основная защита от потока запросов
limit_req_zone задаёт зону в разделяемой памяти, ключуемую по адресу клиента, а limit_req
применяет её. Это важнейшая DDoS-директива в nginx.
# http context — one zone, keyed on the binary client address
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
server {
location / {
limit_req zone=perip burst=20 nodelay;
limit_req_status 429;
}
# A stricter zone on the expensive path
location = /login {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
}
}
rate — устойчивый потолок, burst — допуск на короткий всплеск, nodelay отдаёт всплеск сразу,
а не растягивает его. Зона в 10 МБ вмещает около 160 000 записей IPv4, потому что
$binary_remote_addr хранит адрес в четырёх байтах.
Сначала вводите через пробный прогон. В этом и есть разница между укреплением и простоем:
# Log what WOULD be limited, but let everything through
limit_req_dry_run on;
# Read what the dry-run would have rejected, per client
grep 'limiting requests' /var/log/nginx/error.log | awk '{print $NF}' | sort | uniq -c | sort -rn
Оставьте limit_req_dry_run on на репрезентативную неделю, убедитесь, что клиенты, которых он
ограничил бы, — не ваш реальный трафик, затем выключите, чтобы правило заработало.
3. Ограничение соединений: limit_conn против одновременности
Там, где limit_req ограничивает скорость запросов, limit_conn ограничивает число
одновременных соединений на источник. Это защита от клиента, который открывает и держит много
соединений.
limit_conn_zone $binary_remote_addr zone=connperip:10m;
server {
location / {
limit_conn connperip 20; # max 20 concurrent connections per source
limit_conn_status 429;
}
}
# Rejections show in the access log as the status you set
awk '$2==429' /var/log/nginx/access.log | wc -l
4. Таймауты: превращение почти-невосприимчивости к Slowloris в настоящую
nginx не тратит поток на соединение, поэтому медленное соединение дёшево. Но не бесплатно, потому что дескриптор конечен. Таймауты вытесняют зависшие соединения прежде, чем они накопятся:
client_header_timeout 5s; # time allowed to send the full request header
client_body_timeout 5s; # time allowed between body reads
send_timeout 10s; # time allowed between successful writes to the client
keepalive_timeout 30s; # idle keep-alive lifetime
keepalive_requests 100; # requests per keep-alive connection
reset_timedout_connection on; # send RST on timeout, freeing the socket immediately
Короткие таймауты заголовка и тела — это прямой ответ Slowloris. Соединение, роняющее заголовки по байту, отбрасывается на пятой секунде, не заняв дескриптор на минуты.
# Timed-out connections appear in the error log at 'info'
grep -Ei 'timed out|timeout' /var/log/nginx/error.log | tail
5. Лимиты буферов и размеров: закрываем поверхность больших запросов
Атакующий может исчерпать память и слишком большими заголовками или телом. Ограничьте их:
client_max_body_size 10m; # reject bodies larger than this (413)
large_client_header_buffers 4 8k; # count and size of large header buffers
client_header_buffer_size 1k;
# 413 (body too large) and 400 (bad/oversized header) rates
awk '$2==413 || $2==400' /var/log/nginx/access.log | wc -l
6. Правильно взять IP клиента за прокси
Каждая зона скорости и соединений ключуется по адресу клиента. За балансировщиком или CDN этот адрес, пока вы его не поправите, — адрес прокси. Тогда все реальные клиенты делят одно ведро и лимиты теряют смысл.
# Trust the proxy ranges and read the real client from its header
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Confirm the log now shows real client IPs, not the proxy's single address
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l
Если это число равно 1, каждый запрос приписывается прокси и лимиты ключуются неверно. Это самая частая причина, по которой тщательно написанный лимит либо режет всех, либо никого.
7. Отдавать дешёвый ответ под лимитом
Когда лимит срабатывает, отдавайте что-то дешёвое, а не страницу ошибки, которая сама стоит работы:
limit_req_status 429;
limit_conn_status 429;
error_page 429 = @ratelimited;
location @ratelimited {
default_type text/plain;
return 429 "Too Many Requests\n";
}
Статический 429 почти ничего не стоит отдать, поэтому лимит не превращается в собственное маленькое усиление.
Сигналы, которые стоит завести в мониторинг
# 1. 429 rate — the limits firing
awk '$2==429' /var/log/nginx/access.log | wc -l
# 2. Active vs ceiling — worker saturation
curl -s http://127.0.0.1/nginx_status | awk '/Active/{print $3}'
# 3. Timed-out (Slowloris) connections
grep -c 'timed out' /var/log/nginx/error.log
# 4. Descriptor headroom
cat /proc/$(pgrep -o -x nginx)/limits | grep 'open files'
Растущая доля 429 — это работа лимитов. Приближение Active к worker_connections — это достижение
потолка и сигнал, что скорость запросов переросла то, что способен поглотить хост.
Честный предел укрепления nginx
Всё здесь защищает от исчерпания по скорости запросов и состоянию соединений на краю приложения. Два случая лежат за его пределами.
Первый — заполнение канала: если объём заполняет трубу перед сервером, nginx вообще не получает пакетов и ни одна директива не действует.
Второй — всё, что ниже nginx: SYN-очереди ядра, таблица conntrack и дескрипторы, через которые запрос проходит прежде, чем вообще достигнет nginx. Это предмет руководства по укреплению Linux, и укрепление nginx исходит из того, что они уже на месте. Выше канала ответ — сетевой уровень. nginx держит край приложения, пока тот уровень не включится. Это его работа, и не больше того.
Порядок применения
- Включите
stub_statusи расширенный лог, запишите базовую неделю. - Задайте
worker_connectionsиworker_rlimit_nofile, убедитесь, что лимит ОС их превышает. - Если впереди есть прокси, поправьте
real_ipпрежде, чем писать хоть один лимит. - Добавьте зоны
limit_reqиlimit_connсlimit_req_dry_run on, понаблюдайте неделю. - Выключите пробный прогон, чтобы правило заработало, следите за долей 429.
- Задайте таймауты и лимиты размеров.
- Заведите четыре сигнала в мониторинг.
Третий шаг стоит перед лимитами намеренно: лимит, ключуемый по неверному адресу, хуже, чем никакого, потому что выглядит защитой, ничего не защищая.
Частые вопросы
- Разве nginx уже не невосприимчив к Slowloris?
- В значительной степени невосприимчив, и это стоит сформулировать точно. Slowloris работает, занимая поток воркера на каждое медленное соединение. nginx использует цикл событий, поэтому медленное соединение стоит дескриптора файла и немного памяти, а не потока, и тысячи таких соединений наносят куда меньше вреда, чем серверу с потоком на соединение. Но «куда меньше» — это не «никакого». Дескрипторы всё равно конечны, поэтому client_header_timeout и client_body_timeout по-прежнему важны; именно они превращают почти-невосприимчивость в настоящую, вытесняя зависшие соединения.
- limit_req использовать с burst и nodelay или с delay?
- Для большинства публичных точек — burst с nodelay. Простой limit_req мгновенно отклоняет всё сверх скорости и наказывает законные всплески, например загрузку страницей своих ресурсов. burst добавляет очередь, поглощающую короткий всплеск. nodelay отдаёт запросы из очереди сразу, а не растягивает их, чего и ждёт браузер. delay нужен для более редкого случая, когда трафик надо сгладить перед бэкендом, не выдерживающим всплесков. Задайте низкую скорость и щедрый burst, затем прочитайте долю 429 прежде, чем затягивать.
- Что такое $binary_remote_addr и почему не $remote_addr?
- Оба ключуют зону по адресу клиента. $binary_remote_addr хранит адрес в 4 байтах для IPv4, а не строкой, поэтому фиксированный размер зоны вмещает намного больше записей. Зона в 10 МБ вмещает около 160 000 записей IPv4 в двоичной форме. Суть в ёмкости: при атаке со множества адресов зона не должна переполниться, иначе новых законных клиентов вытеснят вместе с атакующими. Поэтому всегда ключуйте зоны скорости и соединений по двоичной форме.
- Как ввести ограничение скорости, не отрезав реальных пользователей?
- Через limit_req_dry_run on. Он выполняет всю логику ограничения и пишет в error log то, что отклонил бы, но пропускает каждый запрос. Оставьте его включённым на репрезентативную неделю, прочитайте, каких клиентов и при какой скорости он ограничил бы, убедитесь, что это не ваш реальный трафик, и только тогда выключите, чтобы правило заработало. Ввести limit_req сразу в боевом режиме, да ещё настроив по числу из руководства, — верный способ ограничить собственную главную страницу занятым утром.
- Что ломается за балансировщиком или CDN?
- Ключ. Если nginx стоит за прокси, $binary_remote_addr — это адрес прокси, поэтому все клиенты делят одно ведро скорости и лимит теряет смысл. Сначала нужно настроить реальный IP клиента: set_real_ip_from для диапазонов прокси и real_ip_header для заголовка, который он присылает. Тогда зона ключуется по настоящему клиенту. Ошибка здесь — самая частая причина, по которой аккуратно написанный лимит либо не делает ничего, либо режет всех разом.
- Останавливают ли эти директивы объёмную атаку?
- Нет. Если атака заполняет канал перед сервером, nginx вообще не получает пакетов и ни одна директива не действует. Всё здесь защищает от исчерпания по скорости запросов и состоянию соединений на краю приложения. Объём выше ёмкости канала — задача сетевого уровня, и в nginx.conf он не решается.
Опубликовано: август 2026 г.
Руководство обновляется по мере выхода новых моделей и условий лицензирования. Как мы сравниваем производителей