Что такое log flooding в Nginx и чем он опасен
Под log flooding обычно понимают ситуацию, когда Nginx начинает писать в логи слишком много однотипных строк: в access_log — миллионы запросов от ботов/сканеров, в error_log — повторяющиеся предупреждения об апстримах, таймаутах, TLS, переполненных буферах, ошибках чтения файлов и т.п. Иногда «потоп» выглядит как лавина 499/444 в access-логе или как бесконечные upstream timed out и connect() failed в error-логе.
Опасность не только в занятых гигабайтах. Частые записи в файл увеличивают I/O, создают лишнюю нагрузку на CPU (форматирование строк логов), ускоряют ротацию и усложняют расследования: важные события тонут в шуме. Плюс при переполнении диска страдает весь сервер: от баз данных до возможности деплоя.
Цель не «выключить логи», а сделать их управляемыми: меньше шума, больше сигналов, и чтобы при инциденте вы могли быстро поднять детализацию без перезапуска всего стека.
Диагностика: что именно льётся — access_log или error_log
Быстро понять источник
Сначала определите, какой файл растёт быстрее. На большинстве систем Nginx пишет в /var/log/nginx/access.log и /var/log/nginx/error.log, но путь может быть любым (особенно в контейнерах и при логировании в syslog/journald).
sudo ls -lh /var/log/nginx
sudo du -h --max-depth=1 /var/log/nginx
sudo stat /var/log/nginx/access.log
sudo stat /var/log/nginx/error.log
Если «льёт» access-лог — почти всегда это трафик, боты, неудачные запросы, healthchecks. Если «льёт» error-лог — чаще всего это повторяющиеся ошибки апстрима, проблемы с файловой системой, TLS/HTTP2/клиентским поведением, неверная конфигурация или слишком «болтливый» уровень логирования.
Найти самые частые строки
Для error-лога полезно найти топ повторений. Ниже пример, который вырезает «хвост» сообщения начиная с маркера уровня ([error], [warn], [notice]), а затем считает повторы:
sudo awk '{for (i=1;i<=NF;i++) if ($i ~ /\[error\]|\[warn\]|\[notice\]/) {print substr($0, index($0,$i)); break}}' /var/log/nginx/error.log | sort | uniq -c | sort -nr | head
Для access-лога всё зависит от формата, но чаще всего достаточно сгруппировать по статусу, IP и URI (если реальный IP корректно проброшен):
sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Если у вас JSON-логи — парсить лучше через jq, но принцип тот же: выяснить, какие именно запросы, статусы и хосты дают основной объём.

Уровни и тонкости error_log: почему warn иногда хуже error
Директива error_log управляет тем, какие сообщения попадут в error-лог. Типичная конфигурация:
error_log /var/log/nginx/error.log warn;
Уровни (упрощённо) идут от более «подробного» к более «строгому»: debug → info → notice → warn → error → crit → alert → emerg.
Частая ошибка: поставить info или debug глобально «на время», забыть и получить log flooding. Но и warn может оказаться слишком многословным на нагруженных проектах — например, когда апстрим периодически деградирует или клиенты регулярно рвут соединения.
Практичный подход к error_log
- Глобально держите уровень
errorилиwarn(зависит от зрелости проекта и частоты «шумных» предупреждений). - Для точечной диагностики включайте
debugтолько на нужномserverили для отдельного файла, и на короткое время. - Старайтесь устранять первопричину повторяющихся ошибок, а не только «глушить» лог.
Как включать debug безопасно
Чтобы debug реально работал, Nginx должен быть собран с поддержкой debug (в большинстве дистрибутивных пакетов она есть, но не всегда). Включать debug лучше в отдельный файл:
error_log /var/log/nginx/error-debug.log debug;
В проде не включайте debug «широко»: объём логов растёт кратно. Самый безопасный путь — временно ограничить область: отдельный тестовый server (другой порт/хост), либо воспроизведение на стенде.
Access-лог: условное логирование (conditional logging) через map
Главный инструмент против log flooding в access_log — директива access_log ... if= в связке с map. Важный момент: в Nginx нельзя использовать if «как в языках программирования» везде подряд. Зато map — быстрый механизм вычисления переменных, отлично подходящий для условий и классификаций, в том числе для условного логирования.
Базовый шаблон: логировать только «нужное»
Идея простая: вычисляем переменную, затем пишем access-лог только если она равна 1.
map $status $log_by_status {
default 1;
200 0;
301 0;
304 0;
}
server {
access_log /var/log/nginx/access.log main if=$log_by_status;
}
Это радикальный пример: он резко снижает объём логов, отсекая «нормальные» ответы. В реальности чаще делают тоньше: исключают healthchecks, статику, «мусорные» 404 от ботов, а ошибки и подозрительное — оставляют.
Не логировать healthchecks и «шумные» URI
map $request_uri $log_by_uri {
default 1;
~^/healthz$ 0;
~^/metrics$ 0;
~^/favicon\.ico$ 0;
}
server {
access_log /var/log/nginx/access.log main if=$log_by_uri;
}
Если условий несколько, обычно делают несколько map и «склеивают» итоговую переменную.
Комбинация условий: URI + статус + User-Agent
Пример логики: не логируем успешные ответы на «тихие» URI (статика, healthchecks), но логируем ошибки и всё, что похоже на активное сканирование.
map $request_uri $is_quiet_uri {
default 0;
~^/healthz$ 1;
~^/metrics$ 1;
~*\.(css|js|png|jpg|jpeg|gif|ico|svg|webp|avif)$ 1;
}
map $status $is_error_status {
default 0;
~^[45] 1;
}
map $http_user_agent $is_noisy_ua {
default 0;
~*curl 1;
~*wget 1;
~*python-requests 1;
}
map "$is_quiet_uri:$is_error_status:$is_noisy_ua" $loggable {
default 1;
"1:0:0" 0;
}
server {
access_log /var/log/nginx/access.log main if=$loggable;
}
Проверяйте, что ваши условия не «съедают» важное: например, для API статика не актуальна, а для фронта наоборот. Держите изменения минимальными и наблюдаемыми.
Сэмплирование (sampling): логировать 1% запросов
Иногда нужно сохранить «ощущение трафика», но не хранить весь поток. Сэмплировать удобно через split_clients по стабильному ключу (IP, URI, их комбинация):
split_clients "$remote_addr$request_uri" $sample_bucket {
1% 1;
* 0;
}
server {
access_log /var/log/nginx/access.log main if=$sample_bucket;
}
Так вы будете логировать примерно 1% запросов, более-менее равномерно по клиентам и URI. Для расследований можно временно поднять долю до 5–10% и вернуть обратно.
Разделение логов по смыслу
Часто помогает писать «общий» access-лог (с короткой ретеншн-политикой) и отдельный лог только для ошибок 4xx/5xx (с более длинным хранением). Это сильно повышает практическую ценность логов: ошибки легче искать, а диск не «умножает» одно и то же.
map $status $log_errors {
default 0;
~^[45] 1;
}
server {
access_log /var/log/nginx/access.log main;
access_log /var/log/nginx/access-errors.log main if=$log_errors;
}
Если вы активно оптимизируете отдачу статики и форматы (WebP/AVIF), полезно также навести порядок в исключениях по расширениям. По теме кеширования и маппинга форматов может пригодиться материал: как использовать map для WebP/AVIF и кеширования в Nginx.
Почему if опасен, и где он уместен
В поисковых запросах часто рядом встречаются map, if и conditional logging. Важно различать:
ifвнутриlocationможет приводить к неожиданному поведению, если пытаться строить на нём сложную маршрутизацию.mapвычисляется на уровне переменных и обычно безопаснее и производительнее для условий и классификаций.- Для условного логирования конструкция
access_log ... if=— штатная, а условия удобнее готовить черезmap.

Прекращаем flood у источника: limit_req и limit_conn
Условное логирование — это снижение шума. Но если flood вызван реальной нагрузкой от ботов/сканеров, разумно ограничить трафик: иначе вы всё равно платите ресурсами (CPU, соединения, апстримы), просто меньше пишете на диск.
limit_req: ограничение частоты запросов
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
server {
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://backend;
}
}
Это помогает, если access-лог раздувается из-за частых запросов от одних и тех же адресов. Если у вас есть прокси/CDN, ключом зоны должен быть реальный IP, иначе вы ограничите сам прокси. Проверьте, что у вас корректно настроены real IP заголовки и доверенные адреса.
limit_conn: ограничение одновременных соединений
limit_conn_zone $binary_remote_addr zone=connperip:10m;
server {
location / {
limit_conn connperip 20;
proxy_pass http://backend;
}
}
Полезно против клиентов, которые держат много keepalive или длинных запросов, и как «предохранитель» апстрима. На проектах, где пиковая нагрузка упирается в CPU/RAM, такие ограничения проще контролировать на VDS, чем на переполненном общем окружении.
Частые причины error_log flooding и что с ними делать
Upstream timeout / connect failed
Если error-лог забит upstream timed out, connect() failed, no live upstreams, лечить нужно апстрим и таймауты/пулы:
- Проверьте доступность апстрима, DNS и маршрутизацию.
- Сверьте
proxy_connect_timeout,proxy_read_timeout,proxy_send_timeout. - Если это PHP-FPM — проверьте очередь,
pm.max_children, slowlog и время ответа БД.
«Глушить» такие ошибки снижением уровня error_log обычно плохая идея: вы потеряете сигнал о деградации. Лучше уменьшить повторяемость (починить причину) и точечно настроить наблюдаемость.
client prematurely closed connection и 499 как «шум»
В access-логе 499 — это клиент закрыл соединение. Часто это боты, мобильные сети, агрессивные таймауты на стороне прокси или пользователь просто ушёл со страницы. Практика:
- В access-логе можно не логировать
499(черезmap $status), если они вам не нужны для аналитики. - Если
499резко выросли — проверьте, не стали ли ответы медленнее (апстрим, БД, блокировки, GC).
Ошибки открытия файлов и статики: open_file_cache
Если много сообщений про open() или stat() и вы активно отдаёте статику, может помочь open_file_cache: он снижает число обращений к файловой системе и иногда заметно уменьшает нагрузку (а вместе с ней и «сопутствующий» шум).
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
open_file_cache_errors кеширует и ошибки открытия файлов (например, 404 на отсутствующие файлы). Это снижает количество повторяющихся системных вызовов, но учитывайте особенности: если файлы часто появляются «на лету», кеш может задерживать их обнаружение. Тогда уменьшайте open_file_cache_valid и inactive.
Проверка и безопасное внедрение
Любые изменения в логировании и лимитах внедряйте так, чтобы можно было быстро откатить:
- Проверьте конфиг:
nginx -t. - Примените без обрыва соединений:
systemctl reload nginxилиnginx -s reload. - Первые 10–15 минут следите за ростом логов, статус-кодами и ошибками апстрима.
sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log
sudo tail -n 50 /var/log/nginx/access.log
Если вы используете отдельные логи по виртуальным хостам или окружениям, проверьте, что ротация настроена корректно (иначе вы «победите» flood, но получите десятки файлов, которые никто не чистит).
Мини-чеклист: что чаще всего даёт быстрый эффект
- Снизить шум в
access_logчерезmap+access_log if=(исключить healthchecks, статику, типовые «мусорные» запросы). - Вывести ошибки (4xx/5xx) в отдельный файл и держать его дольше.
- Поставить
limit_reqна «дорогие» location (поиск, авторизация, API) и аккуратно на весь сайт. - Не держать глобально
error_log ... debug; для расследований — отдельный debug-файл и короткие интервалы. - При «файловом» шуме и большой статике — включить
open_file_cache, проверив, что это не ломает динамическую генерацию файлов.
Итого
Log flooding в Nginx почти всегда лечится комбинацией двух подходов: (1) уменьшить объём записей без потери смысла через map и conditional logging для access_log; (2) снизить реальную повторяемость событий через limit_req/limit_conn и устранение корневых причин ошибок апстрима вместо «затыкания» error_log. А debug оставляйте как скальпель: точечно, краткосрочно и в отдельный файл.


