В Nginx восемь уровней логирования ошибок: debug, info, notice, warn, error, crit, alert, emerg. Уровень задаётся вторым параметром директивы error_log, первый параметр - путь к файлу, stderr или syslog-сервер. Если уровень не указан, используется значение по умолчанию - error.
Уровни перечислены в порядке возрастания важности и кумулятивны: при установке определённого уровня в лог попадают все сообщения этого уровня и уровней большей важности. Запись error_log /var/log/nginx/error.log warn; даёт warn, error, crit, alert и emerg, а notice, info и debug в файл не попадут. Выбор уровня - это выбор порога: всё, что ниже него, отбрасывается ещё до записи на диск.
Директива error_log работает в блоках http, server и location. Значит, можно держать общий лог на уровне warn, а для проблемного хоста или пути временно поднять детализацию до info или debug, не трогая остальной конфиг.
Как устроены уровни логирования в Nginx и что писать в error_log
Синтаксис простой: error_log <путь> <уровень>. Вместо файла допустимы stderr и syslog:server=unix:/dev/log. Уровень наследуется из родительского блока и переопределяется в дочернем: директива в location перекрывает директиву в server, а та перекрывает http.
Таблица уровней: от debug до emerg
| Уровень | Что попадает в лог | Типичное сообщение | Когда включать |
|---|---|---|---|
| debug | Внутренние события: разбор конфига, выбор location, работа апстримов, таймеры | http upstream request: /api, upstream: 127.0.0.1:8081 | Только для точечной отладки, на минуты |
| info | Информационные сообщения о ходе обработки | client closed connection | Разбор поведения конкретного хоста |
| notice | Нормальные, но заметные события | signal process started, reloading configuration | Контроль применений конфига |
| warn | Предупреждения, работа продолжается | upstream server temporarily disabled | Рабочий уровень на проде |
| error | Ошибки обработки запроса | connect() failed (111: Connection refused) while connecting to upstream | Значение по умолчанию, годится для большинства серверов |
| crit | Критические условия, требуется быстрое вмешательство | сбой при выделении памяти под запрос | Прод, алерты на уровне системы |
| alert | Действие нужно немедленно | не удалось открыть лог-файл или pid-файл | Прод, жёсткий алертинг |
| emerg | Система неработоспособна | старт воркеров невозможен | Прод, аварийный алертинг |
Уровень debug доступен только в сборке с флагом --with-debug. Проверьте её перед тем, как править конфиг:
nginx -V 2>&1 | grep -- --with-debug
Если команда ничего не вывела, директива debug в конфиге приведёт к ошибке разбора при nginx -t, и вы узнаете об этом до применения. Отдельный случай - отладка одного клиента: директива debug_connection 192.0.2.10; включает отладочный лог для отдельных клиентских соединений, для остальных используется уровень, заданный директивой error_log. Отлаживаемые соединения задаются адресом IPv4 или IPv6, а также именем хоста.
Где можно задавать error_log: http, server, location
Разделять логи по хостам удобно, когда на одной машине живёт десяток сайтов. Общий файл быстро превращается в свалку, где grep по одному домену затягивается.
http {
error_log /var/log/nginx/error.log warn;
server {
server_name api.example.com;
error_log /var/log/nginx/api-error.log info;
location /health {
error_log off;
}
}
}
Здесь глобальный уровень warn, для API-хоста - info, а для health-check логирование ошибок отключено. Правило работает и в обе стороны: если в http стоит info, а в конкретном server нужен тихий режим, укажите там error или warn.
Два предостережения. Первое: слишком дробное разделение усложняет поиск, потому что вам придётся помнить, какой хост в какой файл пишет. Второе: локирование location стоит держать только для тех путей, где вы реально ищете проблему. Подробный разбор приоритетов location, proxy_pass и rewrite есть в материале об отладке маршрутизации Nginx.
Как включить debug-логирование для отдельного виртуального хоста без перезапуска сервера
Полный debug на все хосты в проде - плохая идея: объём логов растёт на порядки, а диск и I/O страдают. Точечное включение внутри одного блока server решает задачу без влияния на остальные сайты.
- Проверьте сборку: nginx -V 2>&1 | grep -- --with-debug.
- В блоке server нужного хоста добавьте отдельный файл и уровень debug: error_log /var/log/nginx/example.com-debug.log debug;
- Проверьте синтаксис: nginx -t.
- Примените graceful reload: systemctl reload nginx или nginx -s reload.
- Воспроизведите проблему и смотрите лог: tail -f /var/log/nginx/example.com-debug.log.
- После отладки верните warn или error и снова выполните reload.
server {
listen 80;
server_name example.com;
error_log /var/log/nginx/example.com-debug.log debug;
access_log /var/log/nginx/example.com-access.log main;
}
Проверка сборки и безопасный graceful reload
Разница между reload и restart принципиальна. Restart убивает воркеры и рвёт активные соединения, reload передаёт новую конфигурацию без обрыва уже открытых запросов: старые воркеры дорабатывают текущие сессии, новые поднимаются с обновлённым конфигом. Такой же механизм применяют при обновлении upstream, поэтому менять логи на живом сервере безопасно в рабочее время - этот подход описан в разборе деплоя без простоя.
Перед применением сохраните копию рабочего конфига, чтобы откатиться одной командой:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak nginx -t systemctl reload nginx
Как не утопить прод в debug-логах
Debug-запись генерируется на каждый запрос и на каждое внутреннее событие: поиск location, разрешение апстрима, таймеры. На хосте с сотнями RPS файл вырастает в гигабайты за часы.
- Включайте debug для одного server или одного location, а не глобально.
- Держите период отладки в минутах, затем возвращайте рабочий уровень.
- Направляйте debug в отдельный файл, а не в общий error.log, иначе рабочие сообщения утонут в отладочных.
- Следите за размером: ls -lh /var/log/nginx/ и du -h /var/log/nginx/ покажут рост.
- Не включайте debug на локации со статикой и высоким RPS.
- Ставьте права 640 и владельца root:adm: в debug-сообщениях видны внутренние детали, IP и адреса бэкендов.
Настройка access_log: формат, разделение и уровни
Директива access_log записывает запросы, а структуру записи определяет log_format. Уровней у access_log нет, но их роль выполняет фильтрация по коду ответа, и об этом ниже. Формат combined идёт из коробки, для отладки его расширяют полями апстрима.
log_format: какие поля добавить для отладки
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time '
'ua=$upstream_addr us=$upstream_status';
Что даёт каждое поле:
- $request_time - полное время обработки запроса на стороне Nginx.
- $upstream_connect_time - время установки соединения с бэкендом.
- $upstream_header_time и $upstream_response_time - время до первого байта заголовков и до полного ответа бэкенда.
- $upstream_addr и $upstream_status - адрес и код ответа бэкенда.
Пустой $upstream_status означает, что запрос до апстрима не дошёл: отдала статика, сработало правило deny или соединение отвалилось раньше. Разница между $request_time и $upstream_response_time показывает, где теряется время: у клиента или у бэкенда. Набор метрик для алертов по этим величинам разобран в шпаргалке по метрикам Nginx.
Не пишите в access_log заголовки Authorization, Cookie и access_token. Формат лога - это файл на диске, который читают дежурные, копируют в тикеты и отправляют в внешние системы; токен в такой записи превращается в утечку.
Разделение access_log по кодам ответа через map
Самый частый сценарий: общий лог разрастается, а найти в нём пятисотые ошибки нужно за секунды. Условное логирование в Nginx включается директивой access_log с параметром if= и позволяет исключать из access-лога неважные записи. Директива map превращает код ответа в признак и позволяет писать в отдельный файл только проблемные запросы.
http {
map $status $loggable {
~^[23] 0;
default 1;
}
server {
server_name example.com;
access_log /var/log/nginx/example.com-access.log main;
access_log /var/log/nginx/example.com-errors.log main if=$loggable;
}
}
В example.com-errors.log попадут коды 4xx и 5xx, в основной файл - всё остальное. map вычисляется на каждый запрос, накладные расходы при этом минимальны. Тот же приём работает для разделения по хостам: достаточно объявить отдельный access_log внутри каждого server.
Для health-check, статики и внутренних проб логирование запросов отключают директивой access_log off; внутри location - это убирает шум из файлов.
Отладка 502 и 504 ошибок с помощью логов
502 Bad Gateway означает, что Nginx не получил корректный ответ от апстрима: бэкенд не слушает порт, упал, вернул битый ответ или соединение оборвалось. 504 Gateway Timeout означает, что бэкенд не ответил в отведённое время. Диагноз ставится по строкам в error.log и подтверждается полями апстрима в access.log.
Что искать в error_log при 502
Маркеры, по которым причина определяется за минуту:
- connect() failed (111: Connection refused) while connecting to upstream - бэкенд не слушает порт: процесс упал, не стартовал или слушает другой порт.
- no live upstreams while connecting to upstream - все серверы группы помечены как недоступные после серии неудач.
- upstream prematurely closed connection while reading response header from upstream - бэкенд оборвал соединение до отправки заголовков.
- recv() failed (104: Connection reset by peer) while reading response header from upstream - соединение сброшено на стороне бэкенда.
grep -i 'upstream' /var/log/nginx/error.log | tail -50
Если в логе видно, что недоступен один адрес из группы, посмотрите на конфигурацию апстрима и параметры отказоустойчивости:
upstream backend {
server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=10s;
}
Параметр proxy_next_upstream задаёт, какие ответы считать ошибочными и когда передавать запрос следующему серверу. В рабочем конфиге перечисляют error, timeout, invalid_header и коды 5xx: error timeout invalid_header http_500 http_502 http_503 http_504 - полный набор для этого сценария приведён в материале о деплое Go-сервиса за Nginx. Держите оба инстанса в upstream постоянно, а их состояние переключайте через systemd: остановленный инстанс просто не принимает трафик. Бинарники удобно раскладывать по версиям-папкам, например /opt/myapp/releases/2026-09-15-1234/myapp, а симлинки currentA и currentB держать для конкретного инстанса. Юнит myapp@.service с параметром %i как порт-плейсхолдер позволяет запускать systemctl start myapp@8081.service и systemctl start myapp@8082.service независимо. Тогда запись в error.log про 127.0.0.1:8081 сразу показывает, какой инстанс выпал.
Что искать в error_log при 504
Маркеры таймаутов:
- upstream timed out (110: Connection timed out) while reading response header from upstream - бэкенд не прислал заголовки в отведённое время.
- upstream timed out (110: Connection timed out) while reading upstream - заголовки пришли, а тело ответа задерживается.
Сообщение while reading response header from upstream связано с чтением ответа от upstream, то есть с proxy_read_timeout. Значение по умолчанию proxy_send_timeout - 60s; для proxy_connect_timeout и proxy_read_timeout сверяйтесь с документацией вашей версии Nginx, так как в использованных источниках их значения по умолчанию прямо не подтверждены.
| Параметр | Что ограничивает | Значение по умолчанию |
|---|---|---|
| proxy_connect_timeout | Установку соединения с бэкендом | уточните по документации версии |
| proxy_send_timeout | Отправку запроса бэкенду | 60s |
| proxy_read_timeout | Ожидание ответа после отправки запроса | уточните по документации версии |
Поднимать значение без анализа причины - плохая практика: если бэкенд отвечает медленно из-за тяжёлого запроса в БД или внешнего API, увеличение таймаута просто превратит быстрый 504 в долгий 504 и займёт воркеры. Сначала проверьте сам эндпоинт, минуя Nginx, и замерьте тайминги:
curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' http://127.0.0.1:8081/api/health
Связка access_log и error_log для точного диагноза
Одна запись в access.log и одна в error.log в одну секунду дают точный ответ, кто виноват. В access_log видно $status 502 и $upstream_addr 127.0.0.1:8081, а в error.log в то же время - connect() failed к 127.0.0.1:8081. Вывод: упал конкретный инстанс, а не балансировщик и не сеть.
Если в log_format добавлен $request_id, сквозная связка строится по нему. Без этой переменной ищите по времени и адресу апстрима:
grep ' 502 ' /var/log/nginx/example.com-access.log | tail -20 grep '2026-10-01 12:3' /var/log/nginx/error.log
Ротация логов Nginx: logrotate без потери записей
Nginx не умеет ротировать свои логи самостоятельно: он открывает файл при старте и продолжает писать через этот дескриптор, пока что-то не попросит его переоткрыть. Удаление или переименование открытого лог-файла проблему не решает: Nginx продолжит писать по файловому дескриптору в уже несуществующий файл, место на диске не освободится. Рабочий механизм - logrotate с сигналом USR1, который заставляет Nginx закрыть дескрипторы и открыть файлы заново. Сигнал SIGUSR1 отправляется мастер-процессу.
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || true
endscript
}
Разбор строк: daily и rotate 14 дают двухнедельную историю, compress и delaycompress сжимают всё, кроме свежего файла, sharedscripts запускает postrotate один раз на все совпавшие файлы, а notifempty не трогает пустые логи. Путь к pid-файлу зависит от сборки: встречаются /var/run/nginx.pid и /run/nginx.pid, проверьте свой через nginx -t или в конфиге.
logrotate -d /etc/logrotate.d/nginx logrotate -f /etc/logrotate.d/nginx
Первая команда делает dry-run и показывает, что будет сделано, вторая принудительно запускает ротацию - удобно, чтобы проверить конфиг до наступления следующего цикла.
Базовые приёмы анализа логов: команды, которые экономят время
Пять команд закрывают большинство дежурных задач без установки тяжёлых систем.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
awk '$9 ~ /^5/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
tail -f /var/log/nginx/error.log
grep -c ' 502 ' /var/log/nginx/access.log
du -sh /var/log/nginx/
Первая выводит топ кодов ответа, вторая - самые частые URL с 5xx, третья показывает живые ошибки, четвёртая считает 502 за всё время в файле, пятая следит за объёмом каталога. Команды с полем $9 рассчитаны на формат combined, где $remote_addr стоит первым, а код ответа идёт девятым полем; если вы изменили порядок полей в log_format, скорректируйте номер колонки.
Для больших объёмов базовых команд мало: смотрите в сторону goaccess, отправки в Loki или ELK. Разбор готовых регулярных выражений для поиска медленных запросов, всплесков 404 и признаков сканирования приведён в статье о практическом анализе логов Nginx и Apache. Если в логах видны 403 по конкретным адресам, причина обычно в deny, geo или зонах limit_req - их разбор есть в аудите блокировок IP.
Логи содержат IP-адреса, user-agent, а при неаккуратном log_format и токены. Ограничьте доступ: chmod 640 и владелец root:adm, никакого чтения для посторонних пользователей.
Чек-лист: логирование Nginx в проде
- error_log на уровне warn или error в блоке http.
- Отдельный error_log для проблемного хоста на время отладки.
- debug включается точечно и на минуты, сборка проверена через nginx -V.
- access_log использует формат с $request_time и полями $upstream_*.
- 4xx и 5xx вынесены в отдельный файл через map и if=$loggable.
- health-check и внутренние пробы закрыты директивой access_log off.
- logrotate настроен, postrotate отправляет USR1, dry-run пройден.
- Права на файлы логов 640, владелец root:adm.
- В логи не попадают Authorization, Cookie и токены.
- После отладки уровень возвращён к рабочему значению.
- Конфиг применён через systemctl reload nginx, без разрыва активных соединений.
Начните с двух действий: приведите error_log к уровню warn в http и добавьте $request_time с $upstream_response_time в log_format. Дальше ротация через logrotate с USR1 и разделение 4xx/5xx, и у вас появится база, по которой 502 и 504 разбираются за минуты, а не за час чтения одного гигантского файла.