Логи, мониторинг и отладка Nginx

Лог — это чёрный ящик сервера. Когда что-то идёт не так (а оно пойдёт), именно логи Nginx первыми расскажут правду.
«Не знаешь, почему 502? Открой error.log. Не знаешь, кто тебя долбит? Посчитай IP в access.log. Ответ почти всегда там.»

Прод без наблюдаемости — это полёт вслепую. Nginx ведёт два основных лога и умеет отдавать базовые метрики. Научимся их читать и настраивать под себя.

Два главных лога

http {
    access_log /var/log/nginx/access.log;   # каждый запрос
    error_log  /var/log/nginx/error.log warn; # ошибки и предупреждения
}

access.log пишет каждый запрос: кто, когда, что, какой код ответа. error.log — проблемы: недоступный бэкенд, ошибки конфигурации, отказы. Уровень (warn, error, info, debug) задаёт подробность; на проде обычно warn или error.

Свой формат лога

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 '
                'urt=$upstream_response_time';

access_log /var/log/nginx/access.log main;

Переменные вроде $request_time (полное время запроса) и $upstream_response_time (сколько думал бэкенд) бесценны для поиска медленных мест: видно, тормозит сам бэкенд или сеть до него.

Статус-страница

location /nginx_status {
    stub_status;
    allow 127.0.0.1;        # только локально
    deny all;
}

stub_status отдаёт базовые счётчики: активные соединения, принято/обработано запросов, число в состояниях reading/writing/waiting. Это скармливают системам мониторинга.

Распарсим лог-строку на Python

line = '203.0.113.7 - - [10/Oct/2025:13:55:36 +0000] "GET /api/users HTTP/1.1" 200 1340'

import re
m = re.match(r'(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) [^"]+" (\d+) (\d+)', line)
if m:
    ip, when, method, path, status, size = m.groups()
    print("IP:    ", ip)
    print("Время: ", when)
    print("Метод: ", method, path)
    print("Статус:", status, "| Размер:", size, "байт")
    if status.startswith("5"):
        print("  -> ВНИМАНИЕ: серверная ошибка!")
    else:
        print("  -> запрос успешен")

Попробуй сам ▶ Так выглядит разбор одной строки access.log. Накрутив подсчёт по IP или по статусам, ты быстро находишь, кто шлёт больше всех запросов или сколько у тебя пятисоток.

Как работает под капотом

Nginx пишет в лог в конце обработки запроса. Чтобы запись не тормозила воркер на медленном диске, есть буферизация (buffer=) и условное логирование (if=) — например, не писать успешные ответы статики. После ротации логов (logrotate переименовывает файл) Nginx надо сказать переоткрыть дескрипторы: nginx -s reopen (сигнал USR1) — иначе он продолжит писать в уже переименованный/удалённый файл, и место на диске «утечёт».

Частые ошибки

  • Игнорировать error.log при отладке. Самая частая ошибка новичка — гадать вместо того, чтобы посмотреть в лог ошибок.
  • Не настроить ротацию. access.log без ротации съест весь диск за недели.
  • Логировать всю статику. Каждая иконка в лог — гигабайты шума; access_log off для ассетов.
  • Открыть stub_status наружу. Внутренние метрики не для всего интернета — ограничь по IP.

Best practices

  • Кастомный log_format с $request_time и $upstream_response_time — для поиска тормозов.
  • Ротация через logrotate + nginx -s reopen после неё.
  • stub_status только для localhost/мониторинга.
  • Отправляй логи в централизованную систему (ELK, Loki) на нагруженных проектах.

От сырых логов к ответам на вопросы

Логи ценны не сами по себе, а тем, на какие вопросы они отвечают. «Почему сайт медленный?» — добавь в log_format поля $request_time и $upstream_response_time и сравни их: если первое большое, а второе маленькое — тормозит сеть до клиента или сам Nginx; если оба большие — виноват бэкенд. «Кто устроил наплыв?» — посчитай запросы по $remote_addr за период. «Откуда поток 500-х?» — отфильтруй по статусу и посмотри, какие URL их дают. Один продуманный формат лога превращает Nginx в источник наблюдаемости, а не просто в журнал.

На масштабе сырые текстовые логи на диске перестают справляться — их физически тяжело читать глазами, и они занимают много места. Тогда логи отправляют в централизованные системы: связку Elasticsearch + Kibana (ELK), Grafana Loki, облачные сервисы. Туда же стекаются метрики со stub_status (а в продвинутых сборках — с модулей расширенной статистики), и поверх строят дашборды и алерты: «доля 5xx превысила 1% — разбуди дежурного». Но даже без тяжёлой инфраструктуры базовая гигиена обязательна: настрой ротацию логов через logrotate (иначе диск переполнится за недели), не логируй каждую иконку, и помни про nginx -s reopen после ротации. Наблюдаемость — это то, что отличает «работает на моей машине» от настоящего продакшена, где ты узнаёшь о проблеме из дашборда раньше, чем из жалоб пользователей.

Итоги

access.log фиксирует каждый запрос, error.log — проблемы; кастомный log_format с таймингами помогает находить узкие места, а stub_status отдаёт базовые метрики. Не забывай про ротацию и reopen. Логи — первый инструмент диагностики. В финальном уроке соберём всё изученное в один продакшен-готовый конфиг.

Проверьте себя
1. Чем access.log отличается от error.log?
AНичем
Baccess.log пишет каждый запрос (кто/что/код), error.log — проблемы: недоступный бэкенд, ошибки конфигурации
Caccess.log для HTTPS, error.log для HTTP
Derror.log пишет успешные ответы
2. Почему после ротации логов нужен `nginx -s reopen`?
AЧтобы сжать логи
BИначе Nginx продолжит писать в уже переименованный/удалённый файл, и место на диске будет утекать
CЧтобы перезагрузить конфиг
DЧтобы включить HTTPS