Логи сервера на хостинге: это прежде всего два журнала домена. Журнал доступа (access log) записывает каждый запрос к сайту: IP-адрес, время, адрес страницы, код ответа, браузер, а журнал ошибок (error log) фиксирует только сбои веб-сервера и PHP. В панели управления хостингом оба обычно открываются в разделе доменов, а для анализа хватает grep, awk и sort: ниже готовые команды, которые за минуту показывают ботов, подбор паролей, ошибки 5xx и медленные страницы.
Как мы проверяли
10 ноября 2026 года на тестовом стенде: контейнер Ubuntu 24.04.5, nginx 1.24.0 перед Apache 2.4.58 (распространённая схема на хостинге), PHP-FPM 8.3.6, MariaDB 10.11.14, WordPress 6.4.3. Скрипт 9 минут генерировал трафик: обычные посетители, два поисковых робота, подбор паролей к wp-login.php, атака через xmlrpc.php, сканер файлов .env и .git, медленная страница, ошибка PHP и 45-секундная остановка PHP-FPM. Все команды из статьи выполнены на полученных журналах, вывод сокращен. GoAccess 1.8.1 и logrotate 3.21 установлены из репозитория Ubuntu. Документация nginx, PHP-FPM и GoAccess проверена в тот же день. Это поведение тестового сервера, а не данные с серверов RichHost.
Где найти логи сайта на хостинге
- Панель управления хостингом. Обычно в разделе доменов есть журнал доступа и журнал ошибок. Панель показывает последние строки, для быстрой проверки этого достаточно.
- Файлы на сервере. На собственном сервере журналы обычно лежат в
/var/log/nginx/и/var/log/apache2/. На виртуальном хостинге проще открыть их в панели или скачать файл и разобрать у себя теми же командами. Если у вас есть доступ по SSH, вот как подключиться по SSH с любой системы. - Журнал WordPress. Файл
debug.logпоявляется только после включения WP_DEBUG_LOG. Как сделать это без утечки путей наружу, мы показали в статье про ошибку 500 Internal Server Error. - Slowlog PHP-FPM. Журнал медленных PHP-запросов настраивается в пуле PHP-FPM. На виртуальном хостинге это параметр сервера, так что если страница тормозит без видимой причины, попросите поддержку проверить ее в slowlog.
Прошлые дни обычно лежат рядом в файлах с номерами .1, .2.gz и дальше. Почему это важно для поиска, разберем в разделе про ротацию.
Access log и error log: что куда попадает
За 9 минут теста журнал доступа nginx получил 4348 строк. Подбор паролей (563 запроса), атака через xmlrpc.php (117), сканер (90) и агрессивный краулер не оставили ни одной строки в журналах ошибок: для веб-сервера это обычные запросы с кодами 200 и 404. Журнал ошибок пополнился только тогда, когда что-то действительно сломалось: 11 фатальных ошибок PHP и 78 неудачных подключений Apache к PHP-FPM во время его остановки.
Отсюда простое правило: журнал доступа отвечает на вопрос кто, что, когда, с каким кодом и за сколько, а журнал ошибок объясняет, почему случился отказ. Искать нужно в паре, по времени. Вот один и тот же сбой в обоих журналах:
access: 127.0.1.18 - - [10/Nov/2026:14:27:00 +0000] "GET /?s=domen HTTP/1.1" 503 299 ... error: [proxy_fcgi:error] [client 127.0.1.18:0] AH01079: failed to make connection to backend: httpd-UDS
Журнал ошибок nginx за весь тест остался пустым, даже во время 78 ответов 503: их сформировал Apache, а nginx только передал. Открывайте журнал ошибок того звена, которое ответило.
Формат строки: какие поля где
И nginx, и Apache по умолчанию пишут формат combined, поэтому команды ниже одинаково работают с обоими. Если разбить строку по пробелам, IP-адрес будет полем $1, время $4, адрес страницы $7, код ответа $9, размер в байтах $10. Браузер (User-Agent) удобнее брать, разбивая строку по кавычкам: awk -F'"' '{print $6}'.
В связке nginx и Apache журналы отличаются, и это видно на стенде:
- В журнале Apache было 2215 строк против 4348 у nginx: статические файлы (CSS, JS, шрифты) отдает nginx, до Apache они не доходят. Трафик и ботов считайте по журналу nginx.
- nginx пишет время завершения запроса, Apache время его начала. Медленная страница попала в журнал nginx в 14:20:08, а в журнал Apache в 14:20:05.
- Без модуля mod_remoteip Apache за nginx видит в каждой строке адрес 127.0.0.1, и считать IP по его журналу бесполезно. На нашем прошлом стенде именно так ломались блокировки по IP.
Таблица: что ищете, команда, что значит результат
| Что ищете | Команда | Что значит результат |
|---|---|---|
| Кто делает больше всего запросов | awk '{print $1}' access.log | sort | uniq -c | sort -rn | head |
Один IP с сотнями запросов за несколько минут: бот или атака |
| Какие адреса запрашивают чаще всего | awk '{print $7}' access.log | sort | uniq -c | sort -rn | head |
wp-login.php или xmlrpc.php в верхних строках означают атаку; статика наверху нормальна |
| Распределение кодов ответа | awk '{print $9}' access.log | sort | uniq -c | sort -rn |
Доля 404 и 5xx; резкий рост 5xx означает сбой |
| Когда были ошибки 5xx | awk '$9 ~ /^5/ {print substr($4,2,17), $9}' access.log | uniq -c |
Пачка в одну минуту: упал сервис; единичные в разное время: ошибка в коде |
| Нагрузка по минутам | awk '{print substr($4,14,5)}' access.log | uniq -c |
Время всплеска, дальше ищите IP за эту минуту |
| Кем представляются клиенты | awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head |
Честных ботов видно по названию; атаки часто маскируются под Chrome |
| Подбор паролей | grep 'POST /wp-login.php' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head |
Сотни POST с одного адреса: брутфорс; код 302 означает удачный вход |
| Атака через XML-RPC | grep 'POST /xmlrpc.php' access.log | awk '{print $1, $9}' | sort | uniq -c | sort -rn |
Каждый запрос может содержать десятки попыток пароля |
| Сканирование уязвимостей | grep -E '\.(env|git)|\.sql|backup' access.log | awk '{print $1, $7, $9}' | sort | uniq -c |
Поиск секретов и бэкапов; код 404 значит, что файла нет |
| Медленные страницы | команда с rt= в разделе ниже |
Какие адреса отвечают дольше секунды |
| Ошибки PHP | grep -E 'AH01071|PHP (Fatal|Parse) error' error.log | tail -n 3 |
Файл и строка, где упал код |
| То же в архивах | zgrep -c 'POST /wp-login.php' access.log* |
Количество в каждом файле, включая сжатые |
Что показали журналы за 9 минут
Кто нагружает сайт
$ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -4
613 127.0.2.10
563 127.0.3.5
165 127.0.1.10
156 127.0.1.5
Первое место занял краулер AhrefsBot: до 206 запросов в минуту, 278 из них закончились кодом 404, потому что он перебирал несуществующие адреса, и 28,6 МБ трафика против 15,7 МБ у самого активного живого посетителя. Второе место у подбора паролей. Обычные посетители идут дальше со 130-165 запросами, вместе со статикой.
Среди браузеров злоумышленник не выделялся: его строка Chrome/79.0.3945.130 на Windows 7 стояла в списке рядом с настоящими. GoAccess отнес все эти 563 запроса к Chrome. Атаку видно по IP и адресу страницы, а не по названию браузера. Точно так же слово Googlebot в строке ничего не гарантирует: Google советует проверять IP обратным DNS-запросом командой host, имя должно заканчиваться на googlebot.com, google.com или googleusercontent.com и указывать обратно на тот же адрес.
Брутфорс wp-login.php и xmlrpc.php
$ grep 'POST /wp-login.php' access.log | awk '{print substr($4,14,5)}' | uniq -c
13 14:21
140 14:22
141 14:23
141 14:24
128 14:25
Все 563 попытки получили код 200: WordPress после неудачного входа снова показывает форму. Удачный вход на стенде дал 302 с переадресацией в админку, поэтому команда grep 'POST /wp-login.php' access.log | awk '$9==302' покажет, не угадали ли пароль. Атака через XML-RPC еще коварнее: в журнале всего 117 строк с кодом 200, но каждый запрос методом system.multicall содержал 20 попыток, и внутри ответа сервер 20 раз вернул «Incorrect username or password.». Итого 2340 паролей за две минуты и ни одного кода ошибки в журнале.
Сканирование .env и .git
Сканер с подписью zgrab трижды перебрал 30 адресов: /.env, /.git/config, /wp-config.php.bak, /backup.zip, /db.sql и другие. 84 из 90 запросов получили 404, и это хороший результат. Но каждый ответ весил 56 КБ и занимал около 0,04 с: такие запросы проходят через WordPress, потому что файла нет, и правила перезаписи отдают их в index.php. Если сканеры приходят каждый день, дешевле отсекать их правилом веб-сервера.
Ошибки 5xx во времени
$ awk '$9 ~ /^5/ {print substr($4,2,17), $9}' access.log | uniq -c
2 10/Nov/2026:14:22 500
2 10/Nov/2026:14:23 500
3 10/Nov/2026:14:24 500
1 10/Nov/2026:14:25 500
1 10/Nov/2026:14:26 500
78 10/Nov/2026:14:27 503
2 10/Nov/2026:14:28 500
Здесь два разных сценария. Единичные 500 на протяжении всего теста давал один адрес, admin-ajax.php?action=rh_export, и в журнале Apache рядом с каждой стоит AH01071 с точным местом: Call to undefined function rh_export_orders_csv() in .../bench-shop.php:15. А 78 ответов 503 за 45 секунд (с 14:27:00 до 14:27:44) со временем обработки rt=0.000 означают, что отказывал весь сайт: Apache не смог подключиться к PHP-FPM. Без Apache nginx в такой же ситуации отдает 502 Bad Gateway. Если же в браузере код 508, это уже ограничение ресурсов тарифа, про него отдельная статья: ошибка 508 Resource Limit Is Reached.
Медленные запросы: $request_time и slowlog PHP-FPM
Стандартный формат combined не содержит времени обработки. Чтобы оно появилось, в nginx добавляют собственный формат. Мы дописали в конец строки две переменные: $request_time (полное время от первого байта запроса до отправки ответа, в секундах с миллисекундами) и $upstream_response_time (сколько отвечал бэкенд):
log_format main_rt '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main_rt;
После этого среднее время по адресам считается одной командой:
$ awk 'match($0, /rt=[0-9.]+/) {t=substr($0, RSTART+3, RLENGTH-3); n[$7]++; s[$7]+=t}
END {for (u in n) if (n[u]>=5) printf "%.3f %5d %s\n", s[u]/n[u], n[u], u}' access.log | sort -rn | head -4
3.419 48 /kursy-valyut/
0.095 40 /category/uncategorized/
0.090 52 /post-5/
0.081 49 /
Страница курсов валют отвечала 3,5-3,7 с, остальной сайт быстрее 0,1 с. Журнал доступа говорит, где медленно, а почему, показывает slowlog PHP-FPM. В пуле достаточно двух строк:
request_slowlog_timeout = 2s slowlog = /var/log/php-fpm/site-slow.log
По умолчанию параметр равен 0, то есть выключен. На стенде slowlog собрал 46 записей, по одной на каждый успешный запрос к медленной странице. Вот начало одной из них (всего 20 строк стека, это глубина по умолчанию):
[10-Nov-2026 14:22:55] [pool cw13] pid 1056 script_filename = /srv/cw13/www/index.php [0x00007f34d9214010] curl_exec() /srv/cw13/www/wp-includes/Requests/src/Transport/Curl.php:204 [0x00007f34d9213f30] request() /srv/cw13/www/wp-includes/Requests/src/Requests.php:469 ... [0x00007f34d9213b70] wp_remote_get() /srv/cw13/www/wp-content/mu-plugins/bench-shop.php:4 [0x00007f34d9213b00] rh_fetch_exchange_rates() /srv/cw13/www/wp-content/mu-plugins/bench-shop.php:8 [0x00007f34d9213a80] rh_render_rates_widget() /srv/cw13/www/wp-content/mu-plugins/bench-shop.php:12
Стек читают сверху вниз: в момент записи PHP ждал в curl_exec(), то есть ответа внешнего API, а вызвала его функция rh_fetch_exchange_rates() из нашего плагина. Лечится это не на сервере, а в коде: кэшировать курс на час и поставить короткий тайм-аут. Обратите внимание, что PHP-FPM записал в свой журнал GET /index.php, а не адрес страницы: с красивыми ссылками WordPress все идет через index.php, поэтому адрес находят в журнале доступа по тому же времени. Подробнее о поиске причин торможения написано в разборе диагностики проблем хостинга.
Ротация логов: почему grep не видит вчерашних атак
Журналы каждый день переименовываются и сжимаются, иначе они заняли бы весь диск. Типичная конфигурация logrotate:
/var/log/nginx/access.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
[ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
endscript
}
После двух ротаций на стенде остались access.log, access.log.1 (еще не сжат из-за delaycompress) и access.log.2.gz: 1 086 129 байт журнала сжались до 45 278, в 24 раза. А теперь поиск:
$ grep -c "POST /wp-login.php" access.log* access.log:2 access.log.1:3 access.log.2.gz:0 $ zgrep -c "POST /wp-login.php" access.log* access.log:2 access.log.1:3 access.log.2.gz:563
Обычный grep молча вернул 0 для архива, где лежали все 563 попытки подбора. Для сжатых файлов берите zgrep, а для конвейеров с awk склеивайте все вместе: zcat -f access.log* | awk .... Ключ -f пропускает несжатые файлы как есть.
GoAccess: отчет за секунду
Когда нужна общая картина, а не ответ на один вопрос, подойдет GoAccess. Для нашего формата со временем обработки команда такая:
goaccess access.log --log-format='%h %^[%d:%t %^] "%r" %s %b "%R" "%u" rt=%T %^' \ --date-format=%d/%b/%Y --time-format=%T -o report.html
Для обычного формата достаточно --log-format=COMBINED. На стенде журнал теста (4349 строк) обработан за 0,21 с, отчет получился одним HTML-файлом на 648 КБ: топ адресов (wp-login.php первым, 563 запроса со средним временем 25 мс), коды ответа, роботы отдельной группой, 404 и время обработки. Отчет содержит IP-адреса посетителей, поэтому не кладите его в открытую папку сайта.
Если видите X, делайте Y
| Что в журнале | Что делать |
|---|---|
| Сотни POST на wp-login.php с одного или нескольких адресов | Ограничить попытки входа, включить двухфакторную аутентификацию, заблокировать адреса; проверить, нет ли среди них кода 302 |
| Много POST на xmlrpc.php, а мобильные приложения и Jetpack не нужны | Закрыть xmlrpc.php правилом веб-сервера или плагином |
| 404 на .env, .git, бэкапы | Убедиться, что таких файлов действительно нет в корне сайта; частые сканы отсекать правилом |
| Краулер делает сотни запросов в минуту | Проверить, настоящий ли он; ненужного бота ограничить в robots.txt или заблокировать по User-Agent |
| Пачка 502 или 503 в одну минуту | Открыть журнал ошибок за эту минуту: не отвечал PHP-FPM или бэкенд |
| Единичные 500 на одном адресе | Найти AH01071 или PHP Fatal error: там файл и строка |
| Одна страница отвечает секундами | Найти ее в slowlog, кэшировать внешние запросы |
| 403 есть в журнале доступа, в журнале ошибок пусто | Искать плагин безопасности или правило, которое отвечает молча |
Как различать такие отказы, описано в статье про ошибку 403 Forbidden. На всех тарифах виртуального хостинга RichHost есть собственная панель управления с ИИ-помощником, а поддержка работает с 10:00 до 20:00 по Киеву: если указать в письме URL, точное время и строку из журнала, причину найдут гораздо быстрее.
Частые вопросы
Почему в журнале все запросы с адреса 127.0.0.1?
Вы смотрите журнал бэкенда за прокси: Apache видит nginx, а не посетителя. Берите журнал nginx или попросите включить передачу настоящего IP (mod_remoteip).
Код 200 на wp-login.php означает, что злоумышленник вошел?
Нет. Неудачный вход возвращает 200 и снова показывает форму. Удачный вход дает переадресацию 302, ищите именно ее.
Сколько хранятся логи?
Столько, сколько задано в ротации сервера: в примере выше 14 дней. Если расследуете инцидент, сразу скачайте нужные файлы, пока их не удалила ротация.
Зачем логи, если есть Google Analytics?
Аналитика считает только браузеры, которые выполнили JavaScript. Ботов, атаки, ошибки 5xx, запросы к статике и время ответа сервера видно только в журналах.



