Зачем вообще заморачиваться с логами
Логи — это первоисточник правды о том, как реально живет ваш стек: веб-сервер (Nginx или Apache), PHP‑FPM и системные службы. По логам вы поймете, почему пользователи видят 502, что съедает CPU, кто стучится ботами к админке, и на какие страницы приходится пик трафика. Но без дисциплины и инструментов логи быстро превращаются в шум и заполненный диск. Разберем практический минимум: где и что логируется, как навести порядок в файлах, оперативно смотреть статистику через GoAccess, аккуратно крутить ротацию в logrotate и включить простые алерты, которые действительно срабатывают вовремя.
Что и где логируется в стеке
В типовой конфигурации Linux‑сервера логи складываются в каталог /var/log. Веб‑серверы пишут access/error в отдельные файлы, PHP‑FPM ведет свой журнал по пулам, а системный журнал — через journald или rsyslog. Главное — понимать, какие события искать в каждом месте:
- Nginx:
access.log— все HTTP‑запросы;error.log— ошибки уровня процесса и запроса (включая upstream timeout/502/504). - Apache:
access_logиerror_log, аналогично Nginx. - PHP‑FPM:
error_logиз PHP (notice/warning/fatal) иslowlogпо пулам; опциональноaccess.logдля FPM. - Система:
journalctlпо сервисам (nginx.service,php-fpm.service,apache2.service).
Если вы используете VDS с небольшим диском, следите за объемом: некомпрессированные access‑логи активного сайта съедают гигабайты. Решение — грамотная ротация, компрессия и, по возможности, агрегация логов.

Nginx: настраиваем access/error осознанно
Начнем с Nginx. По умолчанию формат access‑лога похож на combined (как в Apache). Для анализа удобно либо оставить совместимый формат (чтобы использовать готовые пресеты в GoAccess), либо перейти на JSON — он проще парсится внешними системами. Однако, если цель — быстрый разбор в консоли, хватит и стандартного combined.
# /etc/nginx/nginx.conf (фрагмент)
log_format combined '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
# Глобально можно отключить логи и включать их в server{} прицельно
access_log off;
error_log /var/log/nginx/error.log warn;
Для каждого сайта лучше разнести логи в свои файлы и задавать уровень ошибок отдельный. Это упрощает анализ и уменьшает шум.
# /etc/nginx/sites-enabled/example.conf (фрагмент)
server {
server_name example.com;
access_log /var/log/nginx/example.access.log combined;
error_log /var/log/nginx/example.error.log notice;
location ~* \.(jpg|jpeg|png|gif|css|js|ico|woff2?)$ {
# Для статики логи редко нужны — снижает шум и I/O
access_log off;
expires 7d;
}
location / {
proxy_pass http://127.0.0.1:8080;
}
}
Полезно уметь повышать детализацию на время дебага: error_log ... info|debug. Но не держите debug в проде — лог заполняется лавинообразно. По теме кэша и статики см. разбор заголовков и вариативности: Cache-Control и ETag. А если вы настраиваете заголовки безопасности, пригодится шпаргалка: HTTP Security Headers.
Apache: LogFormat, CustomLog, ErrorLog
В Apache подход похожий. Основное — настроить LogFormat и привязать его к CustomLog, а также разнести логи по виртуальным хостам. При необходимости — отключить логирование статических директорий через SetEnvIf.
# /etc/apache2/apache2.conf (фрагмент)
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
# /etc/apache2/sites-enabled/example.conf (фрагмент)
<VirtualHost *:80>
ServerName example.com
CustomLog /var/log/apache2/example.access_log combined
ErrorLog /var/log/apache2/example.error_log
# Отключим шум от статики
SetEnvIf Request_URI "\.(gif|jpg|png|css|js|ico|woff2?)$" no_log
CustomLog /var/log/apache2/example.access_log combined env=!no_log
</VirtualHost>
Если выбираете или комбинируете веб‑серверы — сверяйтесь с нашим сравнением: Nginx vs Apache.
PHP‑FPM: error_log, slowlog и (опционально) access.log
За многие «таинственные» 502 отвечает не веб‑сервер, а backend. В случае PHP это PHP‑FPM. Включите slowlog, чтобы видеть, какие скрипты тормозят, и доступный access.log пула — для времени запроса и статуса.
# /etc/php/*/fpm/pool.d/www.conf (фрагмент)
; Путь для ошибок от PHP в рамках пула
php_admin_value[error_log] = /var/log/php-fpm/www.error.log
php_admin_flag[log_errors] = on
; Лог медленных скриптов
slowlog = /var/log/php-fpm/www.slow.log
request_slowlog_timeout = 3s
; Доступный лог пула (не путать с Nginx access)
access.log = /var/log/php-fpm/www.access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}dms %{kilo}Mkb"
Отдельно убедитесь, что в php.ini включен вывод ошибок в файл, а не в браузер, и задан лимит размера лога, если используется ротация через logrotate.
# /etc/php/*/fpm/php.ini (фрагмент)
log_errors = On
error_log = /var/log/php-fpm/php-error.log
Эта связка даст вам прозрачность: фронт (Nginx/Apache) показывает HTTP‑картину, FPM — где и почему тормозит или падает PHP‑код.
GoAccess: честный анализ трафика за минуты
GoAccess — удобный консольный инструмент для интерактивной аналитики access‑логов. Он понимает формат COMBINED из Nginx/Apache, умеет группировать по хостам, урлам, коду ответа, рефереру, времени ответа, IP и многое другое. Прелесть в том, что не нужно поднимать тяжелый стек — поставили пакет, указали путь к логу, получили живую панель.
# Установка (Debian/Ubuntu)
sudo apt update
sudo apt install goaccess
# Базовый запуск для Nginx/Apache в формате combined
sudo goaccess /var/log/nginx/example.access.log --log-format=COMBINED --time-format=%T --date-format=%d/%b/%Y
Если формат ваших логов отличается, укажите шаблон вручную. Для Nginx удобно оставить combined, чтобы не возиться с токенами. В интерактивном интерфейсе стрелками выбираете метрики, с помощью фильтров обрезаете шум (например, исключаете здоровье‑чеки или пути статики). Практический прием: держите отдельный access‑лог для админки/личного кабинета и анализируйте его отдельно — проще поймать всплески 401/403 и груминг‑атаки на пароли.

Ротация логов через logrotate: без сюрпризов
Ротация нужна, чтобы файлы не росли бесконечно, а старые архивировались и удалялись по сроку. Классический инструмент — logrotate. Он запускается ежедневно из cron и читает конфиги из /etc/logrotate.d. Для Nginx рецептура простая: дневная ротация, компрессия, хранение 14 архивов, и после ротации — сигнал на перезагрузку, чтобы процесс начал писать в новый файл.
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload nginx.service || true
endscript
}
Для Apache принцип тот же. Учитывайте, что некоторые демоны не любят copytruncate (это когда файл копируют и обнуляют без сигнала процессу) — надежнее переслать reload или соответствующий сигнал (например, USR1 в Nginx).
# /etc/logrotate.d/apache2
/var/log/apache2/*log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
create 0640 root adm
sharedscripts
postrotate
systemctl reload apache2.service || true
endscript
}
Для PHP‑FPM тоже задайте ротацию. Если у вас несколько пулов — используйте маску.
# /etc/logrotate.d/php-fpm
/var/log/php-fpm/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
create 0640 www-data adm
postrotate
systemctl reload php-fpm.service || true
endscript
}
Проверяйте ротацию без ожидания ночного запуска: sudo logrotate -d /etc/logrotate.conf (dry‑run) и sudo logrotate -f /etc/logrotate.conf (форс). Если после удаления старых логов диск почему‑то не освобождается — возможно, процесс держит удаленный файл открытым. Диагностика: sudo lsof | grep deleted | grep log. На тарифах виртуальный хостинг ротация обычно преднастроена провайдером; на VDS контроль за логами полностью на вас.
Алерты по логам: просто и эффективно
Алерт должен срабатывать мало и метко. Лучше два качественных триггера, чем десять болтливых.
Минимально достаточный набор для веб‑стека: всплеск 5xx по фронту, рост 499/408 (клиентские разрывы, признак сетевых проблем или перегруза), медленные запросы по PHP‑FPM. Ниже — пример простого скрипта, который раз в минуту считает 5xx и ошибки upstream в текущем минутном окне, и при превышении порога шлет письмо локальному администратору. Для продвинутых сценариев подключите системный логгер или внешние системы мониторинга, но начать стоит с простого.
# /usr/local/bin/log-alerts.sh
#!/usr/bin/env bash
set -euo pipefail
NGINX_ACCESS="/var/log/nginx/example.access.log"
NGINX_ERROR="/var/log/nginx/example.error.log"
NOW_MIN=$(date +"%d/%b/%Y:%H:%M")
# Считаем 5xx за текущую минуту
CNT_5XX=$(grep "$NOW_MIN" "$NGINX_ACCESS" | awk '$9 ~ /^5/ {c++} END {print c+0}')
# Ищем upstream timeout/502 в error.log
CNT_UPSTREAM=$(grep "$NOW_MIN" "$NGINX_ERROR" | grep -E "upstream.*(timeout|502|504)" | wc -l || true)
THRESH_5XX=10
THRESH_UP=3
if [ "$CNT_5XX" -ge "$THRESH_5XX" ] || [ "$CNT_UPSTREAM" -ge "$THRESH_UP" ]; then
MSG="$(hostname) nginx issues: 5xx=$CNT_5XX upstream_err=$CNT_UPSTREAM"
echo "$MSG" | mail -s "ALERT: nginx errors" root
fi
Подключаем как systemd‑таймер, чтобы запускать каждую минуту. Это надежнее, чем cron, и проще собирать логи исполнения через journalctl.
# /etc/systemd/system/log-alerts.service
[Unit]
Description=NGINX log alerts
[Service]
Type=oneshot
ExecStart=/usr/local/bin/log-alerts.sh
# /etc/systemd/system/log-alerts.timer
[Unit]
Description=Run log alerts every minute
[Timer]
Persistent=true
[Install]
WantedBy=timers.target
# Активируем
sudo systemctl daemon-reload
sudo systemctl enable --now log-alerts.timer
А что с медленными запросами PHP‑FPM? Раз в N минут проверяйте прирост строк в slowlog и шлите уведомление, если превышен порог. Также полезно алертить на резкий рост max_children reached в php-fpm — это узкое место пула.
# /usr/local/bin/fpm-slowlog-alert.sh
#!/usr/bin/env bash
set -euo pipefail
SLOWLOG="/var/log/php-fpm/www.slow.log"
THRESH=5
LAST_MIN=$(date +"%b %d %H:%M")
CNT=$(grep "$LAST_MIN" "$SLOWLOG" | wc -l || true)
if [ "$CNT" -ge "$THRESH" ]; then
echo "$(hostname) php-fpm slowlog entries: $CNT" | mail -s "ALERT: php-fpm slow requests" root
fi
Снижаем шум и экономим диск
- Отключайте access‑лог для статики и health‑check путей.
- Снижайте уровень
error_logдоwarnилиnoticeв штатной эксплуатации, повышайте доinfo/debugтолько на время расследования. - Используйте
gzip‑компрессию вlogrotateс отложенной компрессией (delaycompress), чтобы не трогать свежий файл. - Разделяйте логи по сайтам/пулам. Один огромный
access.logтруднее анализировать и ротация на нем больнее. - Периодически проверяйте «подвисшие» удаленные логи через
lsof. Это частая причина забитого диска после ротации.
Чтение и быстрая диагностика
tail -F /var/log/nginx/error.log— следим за ошибками в реальном времени; ключ-Fустойчив к ротации.grep -R "upstream timed out" /var/log/nginx— быстрый поиск типовых проблем.awk '{print $9}' access.log | sort | uniq -c | sort -nr— частота кодов ответа.journalctl -u php-fpm.service -S -10m— последние 10 минут системы по FPM.zcat /var/log/nginx/access.log.1.gz | goaccess - --log-format=COMBINED— ретроспективный анализ сжатого лога без распаковки на диск.
Практические пресеты форматов
Хотите видеть время ответа в миллисекундах в Nginx access‑логе — добавьте $request_time. Это мгновенно показывает медленные ручки API и перегретые страницы.
# /etc/nginx/nginx.conf (фрагмент)
log_format timed '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time';
Применяем к нужному сайту:
server {
access_log /var/log/nginx/example.access.log timed;
}
В PHP‑FPM уже есть %{mili}d и %{kilo}M в access.format — ими удобно пользоваться для триажа узких мест без профайлера.
Чек‑лист по логам для продакшена
- Nginx/Apache access/error разнесены по сайтам, статике отключен
access_log. - В access‑логе есть
$request_time(или аналог), в PHP‑FPM включеныslowlogиaccess.logпула. logrotateнастроен:daily,compress,rotate 14,dateext,postrotateсreloadсервиса.- Готовы быстрые команды анализа (
goaccessдля access,grep/awkдля ошибок). - Есть хотя бы два алерта: всплеск 5xx и рост медленных PHP‑запросов.
- Периодическая проверка диска и «удаленных, но занятых» логов через
lsof.
Ошибки, которые встречаются чаще всего
Частая проблема — включили детальный debug в Nginx и забыли выключить. Через пару дней диск заполнен, сайт встал, паника. Другая классика — copytruncate в logrotate для сервиса, который плохо переносит обнуление файла. В результате теряются строки или процесс продолжает писать в старый дескриптор. Третья — «один общий лог на все», что делает расследование инцидента в разы сложнее и мешает точной ротации. И наконец, алерты без порогов и дедупликации: если вы получаете десятки писем в час, скоро перестанете их читать.
Итоги
Логи — фундамент мониторинга и отладки. Отдельные файлы на каждый сайт и пул, понятные форматы с временем ответа, быстрая аналитика через GoAccess, аккуратная ротация логов через logrotate и несколько простых, но точных алертов — этого достаточно, чтобы превратить хаос в управляемую систему. Начните с минимального набора из этой статьи, а дальше подключайте централизованный сбор и более сложные метрики по мере роста проекта. Главное — логи должны работать на вас, а не против.


