Журналы доступа к серверу — история каждого запроса, который получил ваше приложение

Обновлено

Каждый HTTP-запрос, обрабатываемый сервером, оставляет строку в журнале доступа. Вот как его читать — по полю за полем — и какие паттерны стоит наблюдать.

Журналы доступа к серверу — История каждого запроса, который получил ваше приложение 1
Реклама · УДАЛИТЬ?

Каждый HTTP-запрос, обрабатываемый вашим сервером, оставляет строку в журнале доступа. Большинство из этих файлов находятся на диске и растут тихо, пока не сработает предупреждение по диску или не сломается что-то в производственной среде. Тогда все вдруг хотят прочитать эти файлы.

Вот одна строка из сервера, работающего в формате объединённого журнала:

203.0.113.42 - jsmith [09/May/2026:14:32:11 +0000] "GET /api/users/profile HTTP/1.1" 200 1843 "https://example.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"

Восемь полей. Каждое из них — часть доказательств того, что произошло. Давайте пройдём слева направо.

Поля, по одному

1. IP-адрес клиента — 203.0.113.42

IP-адрес, который открыл TCP-соединение с вашим сервером. Это нет неизбежно IP-адрес конечного пользователя. Если вы находитесь за прокси-сервером, CDN или обратным прокси (например, Nginx перед Node-приложением), то это будет IP-адрес прокси. Реальный IP-адрес клиента находится в X-Forwarded-For или X-Real-IP заголовках — и вам нужно явно настроить формат журнала, чтобы захватывать эти заголовки.

В Nginx это выглядит так: добавление $http_x_forwarded_for в вашу log_format инструкцию. В Apache используйте %{X-Forwarded-For}i. Если вы пропустите это и затем получите жалобу на нарушение или потребуете блокировать злоумышленника, вы окажетесь смотрящим на IP-адрес своего прокси-сервера в каждой строке журнала.

2. Идентификатор — -

Всегда дефис. Это поле предназначалось для хранения результата запроса к identd — устаревшего протокола (RFC 1413), который позволял серверам спрашивать у операционной системы, кто владеет процессом, который инициировал соединение. Никто больше не использует identd. Это поле существует, потому что формат общего журнала был стандартизирован, когда identd ещё был актуален. Пропустите его.

3. Имя пользователя авторизации — jsmith

Имя пользователя, заполняемое при использовании HTTP Basic Auth или Digest Auth. Для большинства современных приложений — аутентификация токенами, куки сессии, JWT — это будет дефис. Если вы защищаете административную зону с помощью htpasswd, неудачные входы отображаются как - рядом с кодом 401; успешные — показывают имя пользователя.

4. Временная метка — [09/May/2026:14:32:11 +0000]

Время, когда сервер закончил обработку запроса (а не когда он начал). Формат — day/Mon/year:HH:MM:SS timezone. Смещение часового пояса имеет большее значение, чем люди думают — если ваш сервер работает в UTC, а ваш инструмент анализа или уведомления находится в локальном часовом поясе, то корреляция всплеска в журналах с конкретным инцидентом требует математических преобразований каждый раз. Работайте в UTC.

5. Строка запроса — "GET /api/users/profile HTTP/1.1"

Наиболее информативное поле. Три части: метод HTTP, путь с параметрами запроса и версия протокола.

  • Метод — GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Неожиданные методы на конечных точках стоит отметить.
  • Путь + параметры запроса — рассказывает вам точно, что было запрошено. Параметры запроса могут содержать поисковые термины, идентификаторы и в плохо спроектированных API — токены аутентификации. Логируйте соответствующим образом.
  • Протокол — HTTP/1.0 клиенты в ваших журналах в 2026 году либо являются древними интеграциями, либо что-то, имитирующее старый клиент. HTTP/2 и HTTP/3 логируются как соответствующие версии, если ваш сервер поддерживает их.

6. Код состояния — 200

HTTP-код состояния, который сервер отправил обратно. Это поле является выводом.

  • 2xx — успех. Запрос обработан.
  • 3xx — перенаправление. Клиенту нужно перейти в другое место.
  • 4xx — ошибка клиента. Неверный запрос, отсутствие аутентификации, не найдено. Может быть легитимным использованием или сканированием.
  • 5xx — ошибка сервера. Ваше приложение упало, истекло время ожидания или вернуло бессмысленные данные. Всегда проверяйте эти случаи.

При анализе инцидента, фильтруйте по 5xx сначала. Затем посмотрите на временные метки первых 500 — это когда начался сбой. Всё до этого — это предшествующее состояние; всё после — это радиус распространения.

7. Размер отправленного ответа — 1843

Размер тела ответа в байтах, не считая заголовков. Дефис означает нулевой размер (типично для 304 Not Modified ответов — клиент уже имеет содержимое). Неожиданно большие значения на конечных точках, которые должны возвращать несколько сотен байт, — это красный флаг. Аутентифицированный пользователь, обращающийся к «получить профиль» и получающий 40 МБ, либо является багом, либо кто-то злоупотребляет экспортом данных.

8. Referer — "https://example.com/dashboard"

Откуда клиент пришёл до того, как сделал этот запрос — URL в заголовке браузера. Referer . (В спецификации HTTP в 1996 году допущена опечатка, и мы с ней до сих пор сталкиваемся.) Прямые запросы, закладки и запросы из приложений на уровне ОС приходят с пустым referer. Это поле существует только в формате объединённого журнала; оригинальный формат общего журнала заканчивается на размере отправленного ответа.

Спам referer — фальшивые заголовки referer в массовых запросах, пытающиеся появиться в ваших аналитических данных — ранее был более распространён, но сейчас это почти устарело. Тем не менее, стоит фильтровать его из агрегированных статистик.

9. User Agent — "Mozilla/5.0 (Windows NT 10.0; Win64; x64)…"

Идентификация клиента. User Agents легко подделываются, поэтому рассматривайте это как подсказку, а не как факт. Законные скраперы обычно идентифицируются честно: Googlebot/2.1 (+http://www.google.com/bot.html), Bingbot/2.0. Скреперы, имитирующие браузеры, отправляют полную строку Mozilla/5.0 Chrome. Curl отправляет curl/8.x если кто-то переопределит это через -A.

Паттерны, на которые стоит обратить внимание: идентичные строки user agent, достигающие одинаковых конечных точек со скоростью машины, или user agent, утверждающий, что это iOS Safari, но делающий запросы в паттерне, который не может быть выполнен человеческим браузером (отсутствие изображений, отсутствие CSS, последовательные сканирования продуктовых страниц).

Паттерны, которые встречаются в каждом журнале

Сканирование уязвимостей

Один IP или небольшая подсеть, которая быстро и последовательно обращается к десяткам путей: /.env, /wp-login.php, /phpmyadmin, /admin/config.yml, /.git/config. Все возвращают 404. Это автоматизированный сканер, выполняющий список проверок, а не целенаправленный атака. Они постоянно выполняются на каждом публичном IP.

Вопрос не в том, «почему они сканируют меня» — а в том, «возвращали ли какие-то из этих путей код 200». Если /.env возвращает 200 на вашем сервере, это и есть реальная проблема.

# Check if any sensitive paths actually returned 200
grep -E '"(GET|POST) /(wp-login\.php|\.env|\.git/config|phpmyadmin|admin)' access.log | awk '$9 == 200'

Попытка ввода паролей

Высокий объём POST-запросов к вашему конечному пункту входа, почти все возвращают 401, из распределённого набора IP-адресов. Признак — паттерн: одинаковый конечный пункт, одинаковый размер ответа (ошибка страница), множество разных исходных IP, которые делятся на один ASN. Время ответа также остаётся стабильным — автоматизированные попытки входа выполняются с постоянной скоростью, чтобы избежать ограничения скорости.

# Count POST /login attempts with 401 status by source IP
awk '$6 == "\"POST" && $7 == "/api/login" && $9 == "401" {print $1}' access.log \
  | sort | uniq -c | sort -rn | head -20

Всплеск 404 после деплоя

Вы деплоите новую версию. Всплеск 404. Старые ссылки в письмах, закладках и внешних сайтах указывают на URL, которые больше не существуют. Это нормально — но если 404-ссылки относятся к URL, которые должно существуют в новой версии, это — регрессия маршрутизации. Сравните пути 404 с вашими определениями маршрутов.

Кластеризация медленных ответов

Стандартный формат журнала не включает время ответа. Вам нужно добавить его: в Apache, добавьте %D (микросекунды) к вашей LogFormat; в Nginx, добавьте $request_time в вашу log_format блок. Как только вы получите его:

# Nginx: show requests slower than 3 seconds (request_time in last field)
awk '{if ($NF+0 > 3) print $0}' /var/log/nginx/access.log | head -20

Если медленные запросы концентрируются в определённый временной интервал, это — конфликт — крон-задача, процесс батч-обработки или резервное копирование, нажимающее на базу данных. Если определённый путь постоянно медленный независимо от времени, это — медленный запрос или блокирующий вызов ввода-вывода, который требует профилирования.

Отладка инцидента на основе журналов

Журнал доступа — это временная линия. Когда пользователь сообщает: «что-то сломалось около 3 часов», вы:

  • Фильтруете в диапазоне с 14:50 до 15:10
  • Ищете первый 5xx — это когда начался сбой
  • Проверяете, что изменилось за несколько минут до этого: был ли деплой? Проверка конфигурации? Обновление сертификата?
  • Проверяете, какие пути получили 5xx — это все или один конечный пункт?
  • Проверяете размеры ответов на успешные запросы до и после — что-то начало возвращать обрезанные ответы?

Несколько признаков сбоев, которые стоит знать:

  • Всплеск 502 — умер ваше промежуточное устройство (сбой приложения, исчерпание пула соединений, база данных упала). 502-ошибки начинаются в точный момент времени.
  • Цикл перенаправления — 301/302 от одного IP к одному пути повторяются. Обычно это настройка HTTPS-перенаправления или настройка SSL в Cloudflare, конфлирующая с логикой перенаправления вашего приложения.
  • 200 с нулевым размером — статус 200, но размер отправленного ответа — 0 или -. Ваше приложение приняло запрос, поглотило исключение и вернуло пустое тело. Классический случай необработанной ошибки.
  • Всплеск 413 — клиенты отправляют тела запросов, превышающие ваш лимит. Либо ваш лимит слишком мал для конкретного случая, либо кто-то сканирует уязвимости загрузки.

Если вы работаете с журналами в нескольких форматах — Apache Common, Apache Combined, Nginx default, пользовательские форматы — форматировщик доступа журналов может парсить и аннотировать поля, чтобы вы не приходили к необходимости вручную отображать позиции полей каждый раз, когда переключаетесь между серверами.

Управление журналами, которые вы не настроите

  • Поворот журналовlogrotate настроен на ежедневный поворот, сжатие и хранение 14–30 дней. Без этого, access.log растёт, пока не заполнит диск. Это происходит в производственной среде.
  • Централизованное хранение журналов — как только у вас больше одного сервера, просмотр отдельных файлов журналов не масштабируется. Передавайте в Loki + Grafana, Elasticsearch или в управляемый сервис. Структурированный формат JSON делает запросы намного проще, чем парсинг CLF с помощью awk.
  • Доступ к исходным журналам — файлы журналов могут содержать параметры запроса с токенами, персональными данными пользователей и внутренними путями. Не делайте их доступными для всех. Будьте осознанными в отношении сроков хранения журналов, если вы подпадаете под GDPR или аналогичные нормы.
  • Не логируйте чувствительные параметры запроса — если ваше приложение принимает токены или пароли в виде параметров URL (это не должно, но иногда устаревшие API так делают), фильтруйте их на уровне журнала до того, как они попадут на диск.
Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?