Вывод и логирование ошибок PHP: php.ini, error_log, PHP-FPM и WordPress
Сайт внезапно отдаёт HTTP 500 или просто показывает белую страницу. Разработчик открывает php.ini, видит display_errors = On и ожидает увидеть понятный Fatal error прямо в браузере. Но браузер молчит. В другом случае ошибка видна на странице, а в журнале её нет. Бывает и наоборот: сайт падает, PHP-лог пустой, зато Nginx пишет про upstream.
Причина в том, что у PHP нет одной настройки «показывать ошибки». Нужно разделять несколько задач: какие сообщения учитывает error_reporting, можно ли выводить их посетителю через display_errors, что происходит с ошибками запуска PHP, включена ли запись через log_errors и куда именно отправляются сообщения через error_log.
На VPS к этому добавляются PHP-FPM, Nginx или Apache, разные версии PHP, отдельные конфигурации для CLI и сайта, права на файлы журналов, .user.ini, настройки пула, свободное место на диске и системные ограничения. Поэтому совет «включите display_errors» работает только в самом простом сценарии.
Если сайт упал прямо сейчас, php.ini пока не трогайте. Сначала запишите время сбоя и HTTP-код, определите PHP и SAPI, которые реально обслуживают запрос, проверьте фактические значения настроек и только после этого ищите запись на нужном уровне. Белый экран — симптом, а не диагноз.
Почему PHP выдаёт ошибку, но в браузере ничего не видно?
Отсутствие сообщения на странице не означает, что PHP отработал без ошибок. На рабочем сервере нормальная конфигурация часто устроена как раз наоборот: посетитель получает обычную страницу ошибки или HTTP 500, а техническое сообщение записывается в журнал.
У PHP здесь участвуют несколько разных настроек:
error_reportingопределяет, какие категории ошибок учитываются;display_errorsразрешает или запрещает вывод сообщений во время выполнения скрипта;display_startup_errorsотносится к ошибкам, возникающим во время запуска PHP;log_errorsотвечает за запись ошибок в журнал;error_logопределяет назначение для записей, если оно задано явно.
Например, на рабочем сайте часто используется логика:
error_reporting = E_ALL
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /path/to/php-error.log
Посетитель при такой настройке не должен видеть диагностический текст PHP, но ошибки продолжают собираться для администратора. Это и есть нормальная production-модель: ошибки прячем от пользователя, а не от себя.
Почему display_errors не объясняет все случаи
display_errors работает не в вакууме. Если нужная категория исключена из error_reporting, сообщение может не попасть в обычный поток диагностики. Если приложение устанавливает собственный обработчик ошибок или исключений, вместо стандартного текста PHP пользователь увидит страницу CMS. А если проблема возникает на уровне запуска PHP или PHP-FPM, пользовательский код может вообще не начать нормальное выполнение.
Для ошибок старта существует отдельная директива display_startup_errors. В production её также обычно не включают публично: смысл остаётся тем же — техническое сообщение лучше записать в журнал, а не показывать посетителю.
Есть и другие варианты. CMS может перехватить исключение и показать собственную страницу. PHP-FPM может оборвать обработку запроса до формирования HTML. Nginx может не получить нормальный ответ от upstream и вернуть 502. Поэтому искать только слово Fatal error в браузере бесполезно.
Если браузер молчит, это ещё не повод включать всё подряд.
Как доказать, что PHP вообще выполняет тестовый код
Первое, что имеет смысл зафиксировать, — HTTP-статус и время запроса. В браузере это можно посмотреть в DevTools на вкладке Network. Если проблема воспроизводится в 22:14:37, искать нужно записи этого времени, а не листать весь журнал за сутки.
Для контролируемой проверки можно создать временный PHP-файл:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
trigger_error('PHP diagnostic test', E_USER_WARNING);
Если предупреждение появилось, PHP-код выполняется и вывод ошибок для этого запроса работает. Если страница остаётся пустой, проверяйте фактическую конфигурацию веб-SAPI и серверные журналы. Добавлять ту же директиву ещё в три файла не нужно.
Практический ориентир: сначала определите канал ошибки. Сообщение может отображаться в HTML, записываться в PHP error log, попадать в PHP-FPM, system journal или фиксироваться веб-сервером. Только после этого есть смысл менять конфигурацию.
Как определить, какой php.ini действительно использует сайт?
Редактировать php.ini имеет смысл только после проверки файла, который реально загружен веб-версией PHP. На VPS нередко установлено несколько версий PHP, а команда в SSH использует совсем не тот интерпретатор, который обслуживает сайт.
Обычная ситуация выглядит так: администратор выполняет php --ini, получает путь, включает там display_errors, перезапускает службу — и сайт не меняется. Настройка могла быть правильной. Файл был не тот.
Править первый найденный php.ini — одна из самых быстрых дорог к бесполезной диагностике.
Почему php --ini не всегда показывает конфигурацию сайта
Команда:
php --ini
показывает конфигурацию PHP CLI, то есть интерпретатора, который запускается из консоли. Сайт при этом может работать через PHP-FPM другой версии и читать другой php.ini.
Полезно сразу проверить CLI:
php -v
which php
php --ini
Эти команды отвечают на вопрос «что запускается в консоли», но пока ничего не доказывают про веб-сайт.
Как проверить конфигурацию PHP через веб-запрос
Для диагностики удобнее временно выполнить проверку из того же виртуального хоста, где возникает проблема. Самый известный вариант:
<?php
phpinfo();
В выводе нужно найти как минимум:
- PHP Version — версию PHP сайта;
- Server API — способ запуска PHP;
- Loaded Configuration File — загруженный
php.ini; - Scan this dir for additional .ini files — каталог дополнительных конфигураций;
- Additional .ini files parsed — реально подключённые дополнительные файлы.
После проверки удалите файл с phpinfo(). Такая страница раскрывает версию PHP, пути, загруженные модули, переменные окружения и другие технические данные сервера. Оставлять её публично доступной не нужно.
Как проверить php.ini без полного phpinfo()
Для точечной диагностики полный phpinfo() часто вообще не нужен. PHP умеет вернуть путь к загруженному конфигурационному файлу напрямую:
<?php
var_dump(php_ini_loaded_file());
А список дополнительных ini-файлов можно получить так:
<?php
var_dump(php_ini_scanned_files());
Удобно собрать небольшой временный диагностический файл:
<?php
echo 'PHP: ' . PHP_VERSION . PHP_EOL;
echo 'SAPI: ' . PHP_SAPI . PHP_EOL;
echo 'php.ini: ' . (php_ini_loaded_file() ?: 'not loaded') . PHP_EOL;
echo 'scanned ini: ' . (php_ini_scanned_files() ?: 'none') . PHP_EOL;
echo 'display_errors: ' . ini_get('display_errors') . PHP_EOL;
echo 'log_errors: ' . ini_get('log_errors') . PHP_EOL;
echo 'error_log: ' . ini_get('error_log') . PHP_EOL;
Так вы проверяете ровно те данные, которые нужны для текущей задачи, и не публикуете огромную страницу с конфигурацией PHP.
Как найти настройки, которые пришли из conf.d или PHP-FPM
Даже найденный php.ini может быть не последней точкой настройки. PHP загружает дополнительные ini-файлы, а PHP-FPM способен добавлять значения на уровне конкретного пула.
Например:
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /path/to/pool-error.log
В таком случае правка основного php.ini может не дать ожидаемого эффекта, потому что для сайта действует более специфичная настройка пула.
Последовательность проверки лучше держать одной:
- версия PHP в веб-запросе;
- SAPI;
php_ini_loaded_file();php_ini_scanned_files();- фактические значения через
ini_get(); - конфигурация конкретного PHP-FPM pool.
После этой цепочки уже понятно, какой файл действительно стоит редактировать.
Как временно включить вывод всех PHP-ошибок для диагностики?
Для короткой диагностики на тестовом сайте можно временно разрешить вывод ошибок в браузер. Для этого недостаточно смотреть только на display_errors: нужная категория ошибки должна попадать под текущий error_reporting.
В php.ini базовая диагностическая связка выглядит так:
error_reporting = E_ALL
display_errors = On
Если нужно диагностировать проблемы запуска PHP в контролируемой тестовой среде, отдельно проверяют:
display_startup_errors = On
На публичном production-сайте такую конфигурацию оставлять не стоит.
После изменения серверной конфигурации PHP-FPM обычно требуется перечитать конфигурацию или перезапустить соответствующую службу. Конкретное имя сервиса зависит от системы и установленной версии PHP.
Проверять результат нужно через тот же веб-SAPI:
<?php
var_dump(error_reporting());
var_dump(ini_get('display_errors'));
var_dump(ini_get('display_startup_errors'));
Временное включение через PHP-код
Когда доступ к php.ini отсутствует, для уже выполняющегося скрипта можно попробовать:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
Но здесь есть принципиальное ограничение: код сначала должен начать выполняться. Если в этом же файле находится синтаксическая ошибка, PHP должен разобрать файл ещё до выполнения ini_set(). В таком случае добавленная строка не спасёт диагностику.
То же относится к ошибкам, возникающим раньше пользовательского приложения. Если PHP или PHP-FPM не дошли до выполнения вашего кода, искать проблему нужно уровнем выше.
Почему display_errors нельзя оставлять включённым на production
Сообщение PHP часто раскрывает больше, чем хотелось бы показывать посетителю: полный путь к файлу, имя класса, структуру каталогов, строку исходного кода, название модуля или фрагмент stack trace. Само сообщение не означает взлом, но оно даёт лишнюю информацию о внутреннем устройстве приложения.
На рабочем сайте разумнее придерживаться другой схемы:
error_reporting = E_ALL
display_errors = Off
display_startup_errors = Off
log_errors = On
Ошибки при этом не исчезают. Они просто уходят из публичного ответа в канал, предназначенный для администратора.
Если после включения display_errors в браузере по-прежнему ничего нет, не добавляйте одну и ту же директиву одновременно в php.ini, .user.ini, .htaccess и исходный код. Сначала проверьте фактическое значение через ini_get().
Как настроить запись PHP-ошибок в отдельный лог?
Для production-сервера отдельный журнал удобнее постоянного вывода ошибок на экран: редкий сбой можно разобрать уже после события, не раскрывая технический текст посетителю.
Базовая конфигурация выглядит так:
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /path/to/php-error.log
log_errors = On включает автоматическое логирование PHP-ошибок, а error_log определяет назначение для сообщений. Это может быть путь к файлу, а в некоторых конфигурациях — системный журнал или назначение, которое обрабатывается самим SAPI. Поэтому не стоит заранее считать, что любой PHP обязан писать в отдельный файл внутри /var/log.
Лучше использовать понятное назначение и абсолютный путь
Если вы хотите отдельный файл проекта, задавайте абсолютный путь:
error_log = /srv/example/logs/php-error.log
Этот путь приведён как пример. На реальном сервере место хранения нужно выбирать с учётом структуры проекта, пользователя PHP-FPM, прав, ротации и того, может ли веб-сервер отдать файл посетителю.
Как проверить сам канал error_log
Сначала можно проверить, что PHP способен отправить произвольное сообщение в текущее назначение:
<?php
error_log('PHP diagnostic test: error_log channel works');
echo 'done';
Если строка появилась в ожидаемом месте, канал error_log() работает.
Но это только половина проверки.
Как проверить автоматическое log_errors
Функция error_log() сама явно отправляет сообщение в журнал. Она не доказывает полностью, что обычные ошибки автоматически логируются так, как вы ожидаете через log_errors.
Для второй проверки используйте контролируемое предупреждение:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '0');
trigger_error('PHP automatic logging test', E_USER_WARNING);
echo 'done';
При фактически включённом log_errors такое предупреждение должно попасть в настроенный журнал и при этом не отображаться в HTML.
Теперь доказаны две разные вещи:
error_log()способен писать в назначение;- обычная PHP-ошибка автоматически попадает туда через механизм
log_errors.
Это намного надёжнее, чем один тест и вывод «логирование работает».
Как смотреть только свежие записи
Откройте последние строки:
tail -n 20 /path/to/php-error.log
Или поток новых сообщений:
tail -f /path/to/php-error.log
После этого выполните один проблемный запрос в браузере. Так в терминале появляются именно свежие события, а не смесь ошибок за несколько дней.
Отдельный журнал проекта или общий серверный лог
Общий журнал проще обслуживать централизованно, отдельный — удобнее при нескольких сайтах и независимых PHP-FPM pools. Но отдельный файл должен находиться там, где PHP может в него писать и откуда веб-сервер не отдаст его посетителю как обычный файл сайта.
Если журнал лежит внутри document root, проверьте его по HTTP. Надёжнее хранить технические логи вне публичного каталога проекта.
Почему PHP не пишет ошибки в указанный error_log?
Если log_errors включён, а error_log пуст, повторно прописывать log_errors = On обычно бессмысленно. Сначала проверьте путь и каталог. Потом пользователя PHP-FPM, всю цепочку прав и состояние файловой системы.
Права самого файла — только половина проверки.
Сначала проверьте фактический путь
Не начинайте с chmod. Сначала убедитесь, что сайт вообще использует тот error_log, который вы открыли:
<?php
echo ini_get('error_log');
Затем проверьте файл и каталог:
ls -ld /path/to/log-directory
ls -l /path/to/php-error.log
stat /path/to/php-error.log
Если файл существует, PHP-процессу нужно право записи в файл. Если PHP должен создать файл самостоятельно, ему требуется право записи в каталог.
Например, php-error.log мог создать root, а воркеры сайта работают от отдельного пользователя. Внешне файл есть, путь правильный, но размер не меняется.
Проверьте всю цепочку каталогов через namei
Даже правильные права самого файла не помогут, если PHP-FPM не может пройти через один из родительских каталогов.
Для проверки всей цепочки полезна команда:
namei -l /path/to/php-error.log
Она показывает владельца и права каждого элемента пути. Это часто быстрее, чем вручную выполнять ls -ld для пяти вложенных каталогов.
PHP-процессу нужен доступ не только к конечному файлу, но и возможность пройти через родительские каталоги.
Проверьте, от кого реально работают воркеры PHP-FPM
Не ориентируйтесь только на master-процесс. PHP-FPM master часто запускается с повышенными правами, а запросы сайтов обрабатывают дочерние процессы от других пользователей.
Посмотреть процессы можно так:
ps -eo user,pid,ppid,cmd | grep '[p]hp-fpm'
Ещё надёжнее проверить конфигурацию конкретного пула и директивы user и group.
Если у вас есть административные права, можно дополнительно проверить доступ от имени пользователя пула:
sudo -u <php-fpm-user> test -w /path/to/php-error.log && echo writable
Если файла ещё нет, проверяйте каталог:
sudo -u <php-fpm-user> test -w /path/to/log-directory && echo directory-writable
Имя пользователя нельзя подставлять наугад: сначала найдите его в конфигурации нужного PHP-FPM pool.
Не используйте chmod 777 как универсальную диагностику. Такой подход открывает запись всем локальным пользователям и маскирует причину. Сначала определите, кто должен писать файл, а затем выдайте минимально необходимые права нужному пользователю или группе.
Права правильные, но лог всё равно не создаётся
Вот здесь часто и находится настоящий подвох. Если путь, владелец и права выглядят нормально, проверьте состояние самого хранилища.
Свободное место:
df -h
Количество свободных inode:
df -i
Диск может показывать свободные гигабайты, но новые файлы перестанут создаваться, если закончились inode. Для каталогов с огромным количеством мелких файлов это вполне реальный сценарий.
Дальше проверьте, не смонтирована ли нужная файловая система в режиме только для чтения:
findmnt -T /path/to/log-directory -o TARGET,SOURCE,FSTYPE,OPTIONS
Если в опциях присутствует ro, проблема уже не в PHP-директиве и не в chmod.
SELinux и AppArmor могут запрещать запись при правильных chmod
Обычные UNIX-права не всегда являются последним уровнем контроля. На системе с SELinux или AppArmor запись может блокироваться политикой безопасности, хотя owner, group и mode выглядят правильно.
Для SELinux сначала можно проверить режим:
getenforce
Если SELinux работает в Enforcing, ищите реальные AVC-denial в системных журналах. На системах с auditd может помочь:
ausearch -m AVC -ts recent
Для AppArmor полезно смотреть kernel/system journal на сообщения с упоминанием профиля:
journalctl -k --since "10 minutes ago" | grep -i apparmor
Не отключайте SELinux или AppArmor просто ради проверки. Нужен конкретный deny в журнале, а уже потом корректировка политики или пути.
PHP error log пуст, потому что процесс убило ядро
Есть ещё один неприятный сценарий: PHP-FPM-процесс завершился так резко, что приложение не успело записать обычную PHP-ошибку. Например, процесс мог попасть под OOM Killer при нехватке памяти.
Тогда PHP error log способен остаться пустым, а нужная запись будет в kernel journal:
journalctl -k --since "10 minutes ago" | grep -Ei 'oom|out of memory|killed process'
Если в момент HTTP 500 или 502 видно, что ядро убило PHP-FPM worker из-за памяти, бессмысленно дальше искать Fatal error в коде. Причина находится уровнем ниже.
| Симптом | Что проверить первым |
|---|---|
| Файл не создаётся | Фактический error_log, каталог и права на каталог |
| Файл существует, но остаётся пустым | Пользователя PHP-FPM, права файла и активную конфигурацию |
| CLI пишет сообщение, а сайт нет | Разницу между PHP CLI и PHP-FPM |
| Права выглядят правильными | namei -l, df -h, df -i, SELinux/AppArmor |
| Новые файлы вообще не создаются | Свободные inode и режим файловой системы |
| Сайт отдаёт 500/502, PHP-лог пуст | PHP-FPM, system journal и OOM Killer |
| Сообщения появляются в другом месте | Дополнительные ini-файлы, настройки пула и системное назначение логирования |
Если error_log не создаётся, хотя настройки выглядят правильными: проверьте ini_get('error_log') → каталог → namei -l → пользователя PHP-FPM → df -h → df -i → режим монтирования → SELinux/AppArmor → PHP-FPM и kernel journal.
После теста должно быть понятно одно из двух: PHP действительно пишет в этот файл или проблема находится в другом назначении либо на другом уровне сервера.
Где искать ошибку при PHP-FPM, Nginx и Apache?
При связке веб-сервер + PHP-FPM одного PHP error log недостаточно. Запрос проходит несколько компонентов, и каждый фиксирует свой класс проблем.
Цепочку удобно держать перед глазами:
браузер → Nginx или Apache → PHP-FPM → PHP-код → CMS или приложение.
Если сбой произошёл до запуска пользовательского PHP-кода, в журнале приложения может не оказаться вообще ничего.
Что искать в PHP-FPM
Журнал PHP-FPM полезен, когда проблема относится к пулу или процессам обработки запросов: служба не запустилась, воркер завершился аварийно, конфигурация пула некорректна, процессы упираются в ограничения или веб-сервер не получает нормальный ответ от FPM.
Для systemd-сервиса можно посмотреть свежие события:
journalctl -u <имя-службы-PHP-FPM> --since "10 minutes ago"
Имя службы зависит от ОС, способа установки и версии PHP. Не копируйте название сервиса из чужой инструкции вслепую — сначала найдите реально используемую службу.
Если worker был аварийно завершён системой, дополнительно смотрите kernel journal. PHP error log не обязан содержать запись о событии, которое произошло уже за пределами обычной обработки PHP.
Что показывает Nginx
Nginx error log особенно полезен при 502 и 504. В нём можно увидеть проблемы соединения с upstream, отсутствующий сокет, отказ соединения, закрытый backend или таймаут ожидания ответа.
tail -f <nginx-error-log>
502 — это не диагноз PHP. Это сообщение веб-сервера: с backend что-то пошло не так.
Причиной может быть неправильный путь к сокету, неработающий PHP-FPM, аварийное завершение воркера или другая проблема цепочки.
С 504 похожая логика: веб-сервер слишком долго не получил ожидаемый ответ. Дальше нужно понять, действительно ли PHP выполнял долгий запрос, завис на внешнем ресурсе или вообще не мог нормально обслужить запрос.
Что показывает Apache
При Apache состав журналов зависит от способа запуска PHP. Если PHP работает модулем Apache, картина будет одной; если Apache передаёт запросы в PHP-FPM — другой.
tail -f <apache-error-log>
Поэтому сначала определите архитектуру, а уже затем решайте, какой файл считать первым источником диагностики.
| Симптом | Где смотреть в первую очередь |
|---|---|
| PHP Fatal error в приложении | PHP error log или журнал приложения |
| PHP-FPM не запускается | PHP-FPM и systemd journal |
| Nginx отдаёт 502 Bad Gateway | Nginx error log, PHP-FPM, при необходимости kernel journal |
| Nginx отдаёт 504 Gateway Timeout | Nginx, PHP-FPM и длительность обработки запроса |
| Apache отдаёт 500 | Apache error log, затем PHP с учётом способа подключения |
| Сайт работает, но отдельная функция выдаёт Warning | PHP error log или лог самого приложения |
Один из самых полезных приёмов — искать не «какую-нибудь ошибку», а события конкретного запроса. Запишите время, откройте нужные журналы, воспроизведите проблему один раз и сравните записи. Так становится видно, на каком участке цепочки запрос перестал идти нормально.
Почему ini_set() и .user.ini иногда не помогают?
ini_set() не заменяет серверную конфигурацию. Функция меняет только те параметры, для которых разрешено runtime-изменение, и начинает работать после того, как PHP уже дошёл до выполнения кода.
Когда ini_set() выполняется слишком поздно
Рассмотрим файл с синтаксической ошибкой. Перед выполнением первой инструкции PHP должен разобрать исходный код. Если разбор закончился ошибкой, до этой строки:
ini_set('display_errors', '1');
интерпретатор просто не дойдёт.
Поэтому для ошибок, происходящих до нормального выполнения приложения, диагностические параметры задают выше: в php.ini, дополнительном ini-файле, конфигурации PHP-FPM или панели управления.
Как проверить, сработал ли ini_set()
Не ориентируйтесь на отсутствие сообщения об ошибке. Сразу прочитайте фактическое значение:
<?php
ini_set('display_errors', '1');
var_dump(ini_get('display_errors'));
var_dump(ini_get('error_log'));
var_dump(error_reporting());
Если значение не изменилось, ищите ограничение выше по цепочке.
Где может быть задана PHP-настройка
| Уровень | Пример | Область действия | Можно ли менять через ini_set() | Как проверить |
|---|---|---|---|---|
php.ini |
display_errors = Off |
Конкретный SAPI/установка PHP | Зависит от директивы | php_ini_loaded_file(), ini_get() |
conf.d |
Дополнительный ini-файл | Конкретный SAPI/версия PHP | Зависит от директивы | php_ini_scanned_files() |
PHP-FPM php_value |
php_value[error_log] |
Конкретный pool | Зависит от директивы | ini_get() в веб-запросе |
PHP-FPM php_admin_value |
php_admin_value[error_log] |
Конкретный pool, административный уровень | Обычно нет | Конфигурация пула + ini_get() |
.user.ini |
display_errors=Off |
Каталог и вложенные каталоги при поддерживаемом SAPI | Только для разрешённых директив | ini_get(), параметры user_ini |
ini_set() |
ini_set('display_errors','1') |
Текущий запрос | Это и есть runtime-уровень | Проверить результат ini_get() |
Такая таблица объясняет типичный парадокс: строка в приложении правильная, но PHP всё равно использует другое значение. Проблема не в синтаксисе строки, а в уровне, на котором настройка зафиксирована.
Как работает .user.ini
.user.ini позволяет задавать часть параметров PHP на уровне каталога при поддерживаемом SAPI, в частности в CGI/FastCGI-сценариях. Но сам факт существования файла ещё не доказывает, что PHP его прочитал.
Проверьте имя пользовательского ini-файла:
<?php
var_dump(ini_get('user_ini.filename'));
И параметр кеширования:
<?php
var_dump(ini_get('user_ini.cache_ttl'));
Из-за кеширования изменение .user.ini может примениться не мгновенно. Поэтому после правки не делайте вывод по одному запросу: сначала проверьте фактическое значение нужной директивы через ini_get().
Когда .htaccess вообще участвует в настройке PHP
Совет вида:
php_value display_errors 1
имеет смысл только в серверной конфигурации, где Apache и способ подключения PHP поддерживают такие директивы. Если сайт работает через Nginx + PHP-FPM, файл .htaccess Nginx вообще не обрабатывает.
Даже при Apache с PHP-FPM директивы, рассчитанные на mod_php, могут оказаться неприменимы.
Директива правильная, уровень применения неправильный. Такое встречается постоянно в инструкциях, которые копируют без проверки SAPI.
Почему PHP в консоли показывает одну ошибку, а сайт — другую?
PHP CLI и PHP-FPM нужно считать разными окружениями, пока проверка не доказала обратное. Одинаковый файл способен нормально выполняться из SSH и падать через браузер — или наоборот.
Отличаться могут:
- версия PHP;
- загруженный
php.ini; - дополнительные ini-файлы;
- набор PHP-расширений;
- переменные окружения;
- текущий рабочий каталог;
- пользователь ОС;
- права на файлы;
- назначение
error_log.
Сравните CLI и веб-сайт
В консоли:
php -v
php --ini
php -m
which php
В веб-запросе:
<?php
echo PHP_VERSION . PHP_EOL;
echo PHP_SAPI . PHP_EOL;
echo php_ini_loaded_file() . PHP_EOL;
echo ini_get('error_log') . PHP_EOL;
В SSH работает? Хорошо. Но сайт этим пока ничего не доказал.
Из SSH работает, а из cron падает
Это отдельный сценарий, и он встречается даже тогда, когда версия PHP одинаковая.
Представим скрипт:
<?php
include 'config.php';
При ручном запуске из каталога проекта относительный путь может разрешаться успешно. Cron запускает тот же файл из другого рабочего каталога — и внезапно config.php уже не находится.
Проверить текущий каталог в PHP можно так:
<?php
echo getcwd();
А в shell:
pwd
Для cron лучше использовать явный путь к интерпретатору и абсолютные пути к скриптам:
/usr/bin/php /path/to/script.php >> /path/to/cron-test.log 2>&1
Дополнительно проверяют:
- какой пользователь выполняет cron;
- какой
PATHдоступен задаче; - откуда запускается команда;
- есть ли у пользователя права на config, cache, uploads и другие каталоги;
- совпадает ли набор переменных окружения;
- куда уходит stderr.
Редирект 2>&1 помогает увидеть сообщения самой команды, но не заменяет PHP error_log. Если PHP настроен писать ошибки отдельно, проверять нужно оба канала.
Если ломается cron — проверяйте cron. Успешный запуск того же файла из интерактивной SSH-сессии ещё ничего не подтверждает.
Как включить PHP-логирование в WordPress и не показать ошибки посетителям?
В WordPress есть собственный механизм отладки, который удобно использовать при сбоях темы, плагина или пользовательского кода. Но wp-content/debug.log не заменяет системный PHP error log и тем более журналы PHP-FPM или Nginx.
Типичная ситуация: после обновления плагина WordPress сообщает, что на сайте возникла критическая ошибка. Посетителю stack trace показывать не нужно, но администратору требуется запись для диагностики.
В wp-config.php можно настроить:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
WP_DEBUG включает режим отладки WordPress, WP_DEBUG_LOG разрешает запись диагностических сообщений, а WP_DEBUG_DISPLAY не даёт WordPress выводить их посетителю.
После воспроизведения ошибки обычно проверяют:
wp-content/debug.log
Если там появляется сообщение плагина или темы с именем файла и строкой, можно работать уже на уровне WordPress.
Почему пустой debug.log ещё ничего не доказывает
WordPress должен успеть загрузиться достаточно далеко, чтобы его механизм отладки начал работать. Если проблема происходит раньше — например, PHP-FPM не может обработать запрос, worker аварийно завершается или PHP ломается до нормальной загрузки WordPress — в debug.log полезной записи может не быть.
Диагностическое дерево здесь простое:
- Есть запись в
debug.log. Разбирайте компонент WordPress: плагин, тему, пользовательский код. debug.logпуст. Смотрите PHP error log за то же время.- PHP error log тоже пуст. Проверяйте PHP-FPM.
- Есть 500/502/504. Подключайте error log Nginx или Apache и системный журнал.
Это экономит время: если WordPress ещё не успел нормально стартовать, отключать плагины по одному может быть бессмысленно.
Почему debug.log нельзя считать безопасным только потому, что он «служебный»
В журнале WordPress могут оказаться абсолютные пути, тексты исключений, фрагменты SQL, имена таблиц и другие технические данные приложения.
Если wp-content/debug.log доступен по обычному URL, это уже отдельная проблема. После включения логирования проверьте, что файл нельзя просто скачать извне.
Один из вариантов проверки — запросить его заголовки:
curl -I https://example.com/wp-content/debug.log
Ожидаемая безопасная конфигурация — файл не должен отдаваться как обычный публичный ресурс. Конкретный способ блокировки зависит от веб-сервера и структуры проекта.
Сопоставляйте WordPress и серверные журналы по времени
wp-content/debug.log— уровень WordPress;- PHP error log — уровень интерпретатора;
- PHP-FPM — уровень пула и процессов;
- Nginx или Apache error log — уровень веб-сервера;
- kernel/system journal — аварийные системные события.
Если WordPress-лог показывает ошибку плагина в ту же секунду, когда PHP error log содержит соответствующий Uncaught Error, цепочка подтверждена. Если WordPress молчит, а PHP-FPM или ядро фиксируют аварийное завершение процесса, искать причину только среди плагинов рано.
После завершения диагностики не оставляйте WP_DEBUG включённым без необходимости. Журналы могут быстро расти и содержать технические данные приложения.
Как читать PHP error log и быстро находить настоящую причину?
Большой PHP error log часто выглядит страшнее самой проблемы. В нём вперемешку лежат старые предупреждения, сообщения Deprecated, ошибки разных сайтов и десятки повторов одного события.
Не гонитесь за последней строкой. Часто она уже следствие.
Надёжнее сначала определить время проблемного запроса и анализировать только свежий фрагмент:
tail -n 100 /path/to/php-error.log
Или открыть поток новых сообщений:
tail -f /path/to/php-error.log
После этого один раз воспроизведите проблему. Такой тест резко уменьшает шум.
Fatal error и uncaught exception
Сообщения вроде Fatal error, Uncaught Error или необработанного исключения могут непосредственно останавливать текущий PHP-запрос.
В записи особенно важны:
- тип ошибки;
- текст сообщения;
- путь к файлу;
- номер строки;
- stack trace, если он присутствует;
- время события.
Не исправляйте только строку, на которой выполнение окончательно остановилось, не разобрав текст ошибки. Проблема может прийти из вызываемого метода, автозагрузчика или несовместимого плагина.
Warning и Notice
Warning не обязательно останавливает выполнение PHP. Но это не значит, что предупреждения можно всегда игнорировать. Warning способен указывать на отсутствующий файл, неправильный параметр функции, проблему подключения ресурса или другую неисправность, которая позже приводит к неверному результату.
Notice обычно менее критичен, но при большом количестве таких сообщений журнал быстро забивается шумом. Если один Notice пишется на каждом запросе, проблема становится уже эксплуатационной: лог растёт, диагностика затрудняется, а диск получает постоянные записи.
Deprecated после обновления PHP
После перехода сайта на более новую версию PHP журнал иногда заполняется сообщениями Deprecated. Они указывают, что код использует конструкции, которые требуют обновления или больше не считаются рекомендуемыми.
Само наличие Deprecated ещё не доказывает причину HTTP 500. Сначала найдите запись, совпадающую со временем реального сбоя, и проверьте, нет ли рядом более серьёзного сообщения.
Ищите первичную ошибку, а не самый последний симптом
После одного сбоя приложение способно породить цепочку вторичных сообщений. Например, сначала не загружается обязательный класс, затем код обращается к несуществующему объекту, а потом обработчик ошибки сам получает неполные данные.
Если после одной записи появилось двадцать похожих сообщений, начинайте с первой точки, где нормальное выполнение нарушилось.
Для грубой фильтрации можно использовать:
grep -i "fatal" /path/to/php-error.log
grep -i "uncaught" /path/to/php-error.log
Но фильтр по слову fatal не заменяет чтение журнала: значимое сообщение не обязано содержать именно это слово.
| Тип сообщения | Как воспринимать | Что проверить |
|---|---|---|
| Fatal error | Текущий запрос обычно остановлен | Текст ошибки, файл, строку и предшествующие записи |
| Uncaught exception / Error | Критично для текущей цепочки выполнения | Исключение, stack trace и место вызова |
| Warning | Зависит от контекста | Повлияла ли проблема на результат запроса |
| Notice | Чаще диагностическое сообщение | Код, переменные и частоту повторения |
| Deprecated | Сигнал о совместимости кода | Версию PHP и проблемный компонент |
| Повторяющиеся сообщения | Могут быстро раздувать лог | Первичную причину и частоту возникновения |
После исправления снова выполните один запрос и посмотрите только новый фрагмент журнала. Старые записи никуда не исчезнут, поэтому наличие прежнего Fatal error не означает, что проблема всё ещё воспроизводится.
Как диагностировать PHP-ошибку за 10 минут?
Быстрая диагностика PHP строится не на количестве изменённых настроек, а на последовательности. Если одновременно править php.ini, .user.ini, .htaccess, конфигурацию PHP-FPM и WordPress, после восстановления сайта будет сложно понять, что именно сработало.
Для первого прохода достаточно десяти шагов.
- Зафиксируйте URL, время и HTTP-код. Не начинайте с редактирования конфигурации. Сначала сохраните исходный симптом.
- Проверьте, доходит ли запрос до PHP. Простой временный PHP-файл поможет отделить проблему интерпретатора от сбоя конкретного приложения.
- Определите версию PHP и SAPI сайта. Сравните их с CLI, если использовали SSH-команды.
- Проверьте фактические значения. Нужны
error_reporting,display_errors,log_errorsиerror_log. - Откройте текущий PHP-журнал. Используйте
tail -fили другой доступный способ просмотра свежих записей. - Воспроизведите ошибку один раз. Не обновляйте страницу двадцать раз: иначе журнал забьётся повторениями.
- Если PHP error log пуст, переходите к PHP-FPM. Проверьте состояние пула и свежие записи сервиса.
- При 500, 502 или 504 откройте error log веб-сервера. Если процесс мог быть убит системой, проверьте также kernel journal.
- Если проблема характерна только для WordPress, сравните с debug.log. Смотрите события того же времени.
- Измените одну найденную причину и повторите тест. После каждого изменения должна быть понятна проверка результата.
У хорошей первой диагностики есть один из трёх результатов: найдена конкретная PHP-ошибка, найден журнал, куда она реально уходит, либо получено доказательство, что запрос ломается на другом уровне.
Полезный приём: перед тестом откройте нужный лог через tail -f, затем выполните ровно один проблемный запрос. Так новый блок сообщений почти всегда проще связать с событием, чем при анализе накопленного журнала.
Что проверить перед тем, как считать диагностику законченной
- известна версия PHP, обслуживающая сайт;
- известен веб-SAPI;
- найден реально загруженный
php.ini; - проверены дополнительные ini-файлы;
- проверены
error_reporting,display_errors,log_errorsиerror_log; - понятно, от какого пользователя работает пул PHP-FPM;
- подтверждена возможность записи в журнал;
- проверены свободное место и inode, если файл не создаётся;
- при необходимости проверены PHP-FPM, веб-сервер и kernel journal;
- старые сообщения отделены от текущего теста;
- для WordPress системный PHP-лог не перепутан с
debug.log; - после изменения выполнен повторный контрольный запрос.
Как оставить PHP-логирование включённым после диагностики безопасно?
После устранения ошибки не нужно выбирать между двумя крайностями: показывать весь технический вывод посетителю или полностью выключить диагностику. Для production обычно безопаснее скрыть сообщения в браузере и сохранить серверное логирование.
Базовая схема:
error_reporting = E_ALL
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /path/to/php-error.log
Фактическое значение error_reporting зависит от политики проекта, но критические ошибки не стоит терять только ради «чистого» журнала. Если лог засыпает Warning или Deprecated, лучше найти источник, а не выключить диагностическую информацию целиком.
Журнал не должен быть публичным файлом сайта
PHP-логи могут содержать абсолютные пути, имена файлов, stack trace и другие внутренние данные. Хранить такой файл там, откуда его можно скачать обычным HTTP-запросом, неудачная идея.
После настройки проверьте две вещи независимо:
- тестовое сообщение действительно появляется в журнале;
- тот же файл нельзя получить через публичный URL сайта.
Контролируйте размер лога и свободное место
Если лог не ротировать, один повторяющийся Warning способен довольно быстро превратить диагностический файл в проблему с диском.
Текущий размер файла можно посмотреть так:
ls -lh /path/to/php-error.log
Или:
du -h /path/to/php-error.log
Одновременно полезно следить за файловой системой:
df -h
df -i
Первый вывод показывает использование пространства, второй — inode. Для сервера с большим количеством мелких логов, cache-файлов или сессий второй показатель иногда оказывается неожиданно важнее первого.
Как проверить logrotate, а не просто настроить его
Для постоянной работы нужен механизм ротации: системный logrotate, возможности панели управления или другой принятый на сервере способ.
Конфигурацию системного logrotate можно предварительно проверить в диагностическом режиме:
logrotate -d /etc/logrotate.conf
Команда не заменяет фактическую проверку конкретного правила, но позволяет увидеть, как logrotate интерпретирует конфигурацию.
Главная проверка выполняется после реальной ротации: новый файл появился — ещё полдела. Убедитесь, что PHP продолжил туда писать.
Откройте новый журнал:
tail -f /path/to/php-error.log
И снова создайте контролируемое сообщение или предупреждение.
Если новый файл существует, но запись не появляется, смотрите владельца и права:
ls -l /path/to/php-error.log
Именно после ротации иногда всплывает ситуация, когда старый журнал был доступен PHP-FPM, а новый создан с другим владельцем или режимом доступа.
Почему процесс может продолжать писать в старый файл
Некоторые процессы держат открытый файловый дескриптор. После переименования старого файла процесс способен некоторое время продолжать писать в него, если механизм ротации и приложение не согласованы между собой.
Поэтому проверяйте не только наличие нового файла, но и фактическое появление новых строк именно в нём. Для логирования через разные SAPI и серверные схемы поведение может отличаться.
Не выключайте журнал сразу после исправления ошибки
Редкие PHP-сбои плохо диагностируются задним числом, если логирование полностью отключено. Код может работать неделями и падать только на конкретных данных, при выполнении cron, после обновления плагина или при определённом запросе.
Рабочая production-конфигурация должна отвечать нескольким критериям:
- посетитель не видит техническое сообщение PHP;
- администратор получает запись об ошибке;
- журнал защищён от публичного доступа;
- PHP-FPM имеет корректные права записи;
- размер журнала контролируется;
- есть ротация;
- после ротации проверена реальная запись в новый файл;
- контролируется свободное место и inode.
Если эти условия выполняются, следующий сбой уже не придётся диагностировать с нуля. У администратора останется время события, текст ошибки и понятная точка, с которой можно продолжить проверку. На production ошибки лучше прятать от посетителя, а не от администратора.
WordPress хостинг

