# теория · шаг 1 из 5
Что пишется без тебя
Коротко
nginx ведёт две ленты сразу: access_log - что случилось, error_log - почему.
Обе пишутся до всякой настройки, и обе врут по-своему, если не знать их правил.
access_logпишется после ответа, одной строкой, и время в ней - момент окончания.error_logпишется по ходу дела и имеет восемь уровней. Заданный уровень означает «этот и всё, что серьёзнее», а половина объяснений живёт ниже привычногоwarn.combinedдаёт адрес, запрос, код и размер тела. Ни домена, ни времени обработки, ни ответа бэкенда в нём нет.- В строке ошибки читай номер в скобках, а не фразу: фраза зависит от сборки.
- Где лежат файлы, отвечает
nginx -T, а не догадки о путях.
Если восемь уровней error_log и поля combined знакомы - листай до «Теперь сам».
Сначала ответь сам
Пользователь жалуется: сайт отдаёт 400 Bad Request. Ты открываешь логи. В
access_log строка есть: код 400, запрос виден. В error_log за эту секунду
пусто. В конфиге стоит обычное error_log /var/log/nginx/error.log warn;.
Куда делось объяснение?
Оно записано не было. nginx считает эту ошибку разговором с одним кривым
клиентом и пишет её на уровне info - на два деления ниже warn. Причина
существует, строка существует, но фильтр её отсёк.
Вот она, если понизить уровень:
2026/09/03 15:45:48 [info] 363#363: *133 client sent too long header line:
"X-Big: aaaaaaaaaaaa..." while reading client request headers
На что это похоже
Представь приёмную поликлиники. Есть журнал посещений: кто пришёл, во сколько ушёл, к какому врачу. Строка появляется, когда человек уже вышел, и в ней записан момент выхода, а не входа. По журналу видно поток и видно, что вот этот посетитель ушёл ни с чем, но почему - там не написано.
И есть записи самого врача: что болело, что не сошлось, чего не хватило. Их ведут по ходу приёма. Только вот у записей есть градация важности, и заведующий велел подшивать в папку лишь то, что серьёзнее определённой отметки. Мелочь вроде «пациент принёс не тот полис» до папки не доезжает.
Ты пришёл разбираться с ушедшим ни с чем и смотришь в папку. Её содержимое зависит от того, где заведующий провёл черту.
Механизм: две ленты и одна черта
Уровень лога - метка серьёзности, которую nginx ставит каждой строке
error_log. Уровней восемь, от самого мелкого к самому тяжёлому: debug,
info, notice, warn, error, crit, alert, emerg. Третьим словом в
error_log /путь warn; ты называешь черту, и в файл попадает названный уровень
и всё, что серьёзнее его; остальное nginx сочиняет и выбрасывает. Если уровень
не назван, действует error: так стоит, например, в пакете Debian, где в
nginx.conf написано просто error_log /var/log/nginx/error.log;.
Правило
access_log отвечает на вопрос «что», error_log - на вопрос «почему», и
второй виден ровно настолько, насколько низко опущен уровень.
Разбор: строка combined по костям
127.0.0.1 - - [03/Sep/2026:15:45:16 +0000] "GET /deadapi/x HTTP/1.1" 502 157 "-" "Mozilla/5.0 (X11; Linux) Firefox/1.0"
| Кусок | Переменная | Что означает |
|---|---|---|
127.0.0.1 |
$remote_addr |
кто подключился к nginx |
первый - |
- | просто символ, вписанный в формат |
второй - |
$remote_user |
имя из HTTP-авторизации, обычно пусто |
[03/Sep/2026:15:45:16 +0000] |
$time_local |
момент окончания обработки; +0000 - часовой пояс сервера |
"GET /deadapi/x HTTP/1.1" |
$request |
метод, URI и версия |
502 |
$status |
код, который ушёл клиенту |
157 |
$body_bytes_sent |
байты тела, без заголовков |
"-" "Mozilla..." |
$http_referer, $http_user_agent |
откуда и чем |
Два поля стоит проверить на себе, потому что оба обманывают.
Время - это конец, а не начало. Запрос, который nginx обслуживал шесть секунд, встанет в лог на шесть секунд позже, чем начался. При разборе инцидента это сдвигает всю картину: ищешь в логах бэкенда по времени из строки nginx и не находишь.
$body_bytes_sent - не весь трафик. Для файла в 300 байт замер даёт
$body_bytes_sent = 300 и $bytes_sent = 539: заголовки ответа весят ещё 239
байт. На картинках разница теряется, на мелких ответах API - нет.
Разбор: где наивное правило подводит
Три вещи, из-за которых лог показывает не то, что произошло.
Уровень скрывает половину. Мы уже видели 400. Так же прячется 499 - клиент
ушёл, не дождавшись: в access_log код есть, а объяснение
epoll_wait() reported that client prematurely closed connection идёт на info.
А вот worker_connections are not enough пишется на alert (когда слота не
нашлось) или на warn с хвостом reusing connections (когда nginx закрыл
простаивающие keepalive-соединения - те, что клиент держал открытыми на
будущее, - и справился). alert видно даже при фильтре error, потому что
это беда всего сервера, а не одного запроса; warn при уровне по умолчанию не
пишется.
Номер надёжнее фразы. Одна и та же ошибка на разных сборках выглядит по-разному:
alpine: upstream timed out (110: Operation timed out) while connecting to upstream
debian: upstream timed out (110: Connection timed out) while connecting to upstream
Текст берётся из библиотеки C, номер - из ядра. Ищи по (110:, (2:, (13:, и
твой поиск переживёт переезд на другой образ.
Файлы могут быть не файлами. В официальных образах /var/log/nginx/access.log
- это ссылка на /dev/stdout. Логи забирает движок контейнеров, tail -f по
файлу висит и ничего не показывает (-f - «следить за новыми строками»,
выход - Ctrl+C), а попытка очистить его пишет в вывод. Если в каталоге
тишина, а сайт работает, проверяй это раньше всего:
ls -l /var/log/nginx
total 0
lrwxrwxrwx 1 root root 11 Sep 2 21:04 access.log -> /dev/stdout
lrwxrwxrwx 1 root root 11 Sep 2 21:04 error.log -> /dev/stderr
Стрелка и буква l в начале строки - это ссылки. Такие логи читают командой
docker logs имя-контейнера. А куда nginx пишет вообще, покажет
nginx -T | grep -E "access_log|error_log": собранный конфиг со всеми
include, из которого grep оставит нужные строки (-E разрешает | -
«или», а кавычки не дают оболочке принять | за свою трубу).
Что ломается без этого
Разбор идёт по кругу. Человек видит в access_log код и не находит причины;
делает вывод «в логах ничего нет» и начинает менять конфиг наугад. Через час
выясняется, что причина писалась всё это время, просто на уровень ниже черты.
Второй способ потерять время - искать по фразе из чужой статьи. Статью писали на
Debian, у тебя Alpine, поиск grep "Connection timed out" не находит ничего, и
рождается вывод «у нас другая проблема».
Зачем это в работе
Первое, что делают на новом сервере, - опускают error_log до info и смотрят
сутки. Шума будет много: закрытые keepalive-соединения, ушедшие клиенты. Но
именно там лежат объяснения кодов 400 и 499, по которым иначе нечего сказать,
кроме «у кого-то что-то не так».
Дальше уровень поднимают обратно до warn или error - и понижают точечно,
когда снова понадобится.
Быстрый счёт по кодам за последние десять тысяч запросов:
tail -n 10000 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
awk '{print $9}' печатает девятое поле каждой строки (поля разделены
пробелами, это не переменные nginx), sort сортирует, uniq -c схлопывает
одинаковые строки и считает их, второй sort -rn ставит самые частые наверх.
В выводе слева количество, справа код:
9436 200
381 304
118 404
47 502
18 301
Позиции полей ($1 - адрес, $7 - URI, $9 - код) верны для combined;
поменяешь формат - поменяются и номера. Это одна из причин, по которой в
следующем уроке появится структурированный лог.
Уровень debug требует особой сборки
error_log ... debug; работает, только если nginx собран с --with-debug;
проверяется nginx -V 2>&1 | grep -o with-debug (nginx -V печатает в поток
ошибок, 2>&1 сливает его с обычным выводом, -o оставляет только найденное
слово: есть with-debug - сборка отладочная, пусто - нет). Без него nginx -t скажет
test is successful, конфиг применится, а отладочных строк не появится ни
одной - молчаливый отказ. И даже там, где debug доступен, включать его на весь
сервер не стоит: debug_connection 192.168.1.44; в блоке events пишет отладку
для одного адреса.
Теперь сам
В логе строка с кодом 499 и $body_bytes_sent = 0. В error_log за эту секунду
ничего. Уровень - warn. Сколько ты можешь сказать о причине и что сделать,
чтобы в следующий раз сказать больше?
О причине - почти ничего: 499 означает, что клиент закрыл соединение, не дождавшись
ответа, а кто он и сколько ждал, в этой строке не написано. Чтобы в следующий раз
было что сказать, нужны две вещи: понизить error_log до info (тогда появится
строка про преждевременно закрытое соединение) и добавить в формат время
обработки - без него не отличить «клиент нетерпелив» от «мы слишком медленные».
Первая правка описана выше в этом уроке, вторая - тема следующего.
Главное
access_log пишется одной строкой после ответа, и время в ней - момент
окончания. error_log пишется по ходу дела и фильтруется уровнем: info держит
объяснения 400 и 499, alert - беды всего сервера. В строке ошибки читай номер
в скобках, а не фразу: фраза зависит от сборки. $body_bytes_sent считает только
тело, весь ответ считает $bytes_sent. Где лежат файлы и что вообще включено,
отвечает nginx -T; в контейнерах логи обычно уходят в stdout, а не в файл.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий