Новинка Виртуальный VDS сервер в Нидерландах от 390р
Выберите продукт

Nginx: как остановить log flooding через error_log, map и условное логирование

Разбираем, почему Nginx внезапно начинает «заливать» диски логами, как быстро найти источник (access_log или error_log) и снизить поток записей через map, conditional logging, sampling и лимиты, сохранив наблюдаемость и возможность точечного debug.
Nginx: как остановить log flooding через error_log, map и условное логирование

Что такое 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, но принцип тот же: выяснить, какие именно запросы, статусы и хосты дают основной объём.

Диагностика: какие файлы логов Nginx растут быстрее и какие строки повторяются чаще всего

Уровни и тонкости error_log: почему warn иногда хуже error

Директива error_log управляет тем, какие сообщения попадут в error-лог. Типичная конфигурация:

error_log /var/log/nginx/error.log warn;

Уровни (упрощённо) идут от более «подробного» к более «строгому»: debuginfonoticewarnerrorcritalertemerg.

Частая ошибка: поставить 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 (другой порт/хост), либо воспроизведение на стенде.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды

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.

Пример конфигурации Nginx: map для условий логирования и sampling через split_clients

Прекращаем 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, чем на переполненном общем окружении.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Частые причины 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.

Проверка и безопасное внедрение

Любые изменения в логировании и лимитах внедряйте так, чтобы можно было быстро откатить:

  1. Проверьте конфиг: nginx -t.
  2. Примените без обрыва соединений: systemctl reload nginx или nginx -s reload.
  3. Первые 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 оставляйте как скальпель: точечно, краткосрочно и в отдельный файл.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...