FastCGI и PHP: сообщение stderr об ошибках
Понимание сообщения FastCGI stderr
Сообщение fastcgi sent in stderr: "PHP message: ..." появляется в логах веб-сервера (nginx, Apache) когда PHP-FPM отправляет вывод ошибок через стандартный поток ошибок (stderr) обратно в FastCGI. Это обычное поведение при включенном log_errors и отсутствии отдельного лога. Сообщение может засорять лог сервера, особенно при большом количестве незначительных ошибок. Ниже рассмотрены эффективные решения.
Как полностью убрать сообщение 'fastcgi sent in stderr' из логов nginx, сохранив возможность отслеживать ошибки?
Наиболее надёжный способ - увеличить буфер для stderr в nginx. По умолчанию размер буфера может быть слишком мал, и часть сообщения обрезается, что всё равно вызывает запись в error_log. Установка подходящих значений fastcgi_buffer_size и fastcgi_buffers позволяет nginx полностью прочитать и обработать stderr, после чего сообщение не дублируется в логах ошибок (только если не включён fastcgi_intercept_errors).
server {
listen 80;
server_name example.com;
root /var/www/example;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
}
}
Fastcgi sent in stderr php message (сообщение fastcgi об ошибке в php)
- fastcgi_buffer_size 16k - размер буфера для чтения первой части ответа (включая заголовки и stderr).
- fastcgi_buffers 8 16k - количество и размер буферов для остального тела ответа.
После перезагрузки nginx сообщения 'fastcgi sent in stderr' перестанут появляться, если только размер stderr не превышает суммарный размер буферов (в примере - 128k). Для большинства ошибок этого достаточно.
Возможная проблема: если буферы по-прежнему малы, сообщение будет обрезано, но nginx всё равно выведет предупреждение «upstream sent too big header while reading response header from upstream». В таком случае размер буферов нужно увеличить (например, до 32k и 8 32k).
Цель: избежать дублирования ошибок в лог nginx при сохранении возможности их видеть в логах PHP-FPM.
Как настроить PHP для записи ошибок в отдельный файл, а не через stderr?
Настройка error_log в php.ini или в конфигурации пула PHP-FPM позволяет перенаправить все ошибки PHP в указанный файл, минуя stderr. После этого сообщение 'fastcgi sent in stderr' исчезнет, так как PHP не будет ничего отправлять в stderr (кроме фатальных ошибок, если не установлен error_log).
; /etc/php/8.2/fpm/php.ini
log_errors = On
error_log = /var/log/php_errors.log
; обязательно отключить display_errors для прода
; display_errors = Off
; /etc/php/8.2/fpm/pool.d/www.conf (секция pool)
; можно задать отдельный лог для пула
php_admin_value[error_log] = /var/log/php_pool_errors.log
php_admin_value[log_errors] = 1
После изменения файлов перезапустить PHP-FPM: systemctl restart php8.2-fpm.
Типичная ошибка: забыть создать файл лога или выставить права (www-data или пользователь пула). Решение: touch /var/log/php_errors.log && chown www-data:www-data /var/log/php_errors.log.
Как перехватить все ошибки PHP пользовательским обработчиком, чтобы они не попадали в stderr?
Использование set_error_handler позволяет взять контроль над ошибками и, например, логировать их в базу данных или подавлять. При этом исходный поток stderr не затрагивается.
<?php
set_error_handler(function($severity, $message, $file, $line) {
// Игнорируем ошибки ниже E_WARNING (например, notices)
if (!(error_reporting() & $severity)) return;
// Логируем в файл
error_log("[$severity] $message in $file:$line", 0);
});
trigger_error('Это тестовая ошибка', E_USER_WARNING);
?>
Обратите внимание: фатальные ошибки (E_ERROR) не обрабатываются этим перехватчиком. Для них потребуется register_shutdown_function.
Проблема: если в обработчике допущена другая ошибка, она может быть направлена в stderr. Рекомендуется аккуратно писать логику.
Как отключить вывод stderr в FastCGI через настройки PHP-FPM (catch_workers_output)?
Параметр catch_workers_output в файле пула определяет, будет ли перехватываться stdout и stderr рабочих процессов. Значение no предотвращает передачу этих потоков обратно в FastCGI, и сообщение 'fastcgi sent in stderr' не возникнет. Однако ошибки PHP при этом никуда не запишутся, если не настроен отдельный лог.
; /etc/php/8.2/fpm/pool.d/www.conf
catch_workers_output = no
Цель: радикальное подавление сообщения, но с потерей информации об ошибках. Используется только если ошибки уже логируются иначе.
Крайне не рекомендуется на production без альтернативного логирования - можно пропустить критическую ошибку.
Как увеличить буфер для stderr при использовании Apache с mod_proxy_fcgi?
В Apache для модуля proxy_fcgi существует директива ProxyFCGIBufferSize. Она задаёт размер буфера для чтения ответа от FastCGI, включая stderr.
<Proxy "fcgi://localhost:9000" enablereuse=On max=10>
ProxyFCGIBufferSize 65536
</Proxy>
Значение должно быть больше максимального размера ожидаемого stderr (64K обычно хватает).
Неправильная настройка может привести к ошибке 'FastCGI: comm with server. getpeername: Transport endpoint is not connected'. Нужно перезапустить Apache.
Как отключить display_errors, чтобы избежать вывода ошибок на экран и их потенциального попадания в stderr?
Даже если display_errors выключен, ошибки могут всё равно отправляться в stderr через log_errors. Но в некоторых конфигурациях (особенно при использовании старого mod_fastcgi) отключение display_errors помогает.
; php.ini
display_errors = Off
; обязательно включите log_errors, чтобы видеть ошибки
log_errors = On
error_log = /var/log/php_errors.log
Цель: предотвратить вывод ошибок в stdout/stderr при обработке скрипта, оставив только лог.
На некоторых хостингах display_errors может быть принудительно включен в .htaccess - тогда изменения в php.ini игнорируются.
Расширенные примеры настройки и диагностики
Пример 1. Детальная настройка буферизации nginx с тестовым скриптом
Создадим PHP-скрипт, который генерирует большое сообщение в stderr:
<?php
// test_error.php
$error_message = str_repeat('Test error line ' . PHP_EOL, 1000); // 1000 строк
file_put_contents('php://stderr', $error_message);
echo "Script completed, check nginx logs\n";
?>
При обращении к скрипту без настройки буферов в логах nginx появится:
2025/03/15 10:00:00 [error] 1234#1234: *1 FastCGI sent in stderr: "PHP message: Test error line ..." while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /test_error.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
После добавления в location fastcgi_buffer_size 32k; и fastcgi_buffers 4 32k; сообщение исчезнет (если размер stderr меньше 128k). Вывод скрипта остаётся в теле ответа.
Пример 2. Настройка PHP-FPM пула с логированием в syslog
Вместо файла можно использовать syslog, что упрощает централизованный сбор логов:
; /etc/php/8.2/fpm/pool.d/www.conf
; вместо error_log = /path/to/file
php_admin_value[error_log] = syslog
php_admin_value[log_errors] = 1
; можно задать идентификатор
php_admin_value[syslog.ident] = PHP-FPM
Перезагрузка PHP-FPM. Теперь ошибки будут направляться в системный syslog (обычно /var/log/syslog или /var/log/messages). Сообщение 'fastcgi sent in stderr' исчезнет, так как PHP не пишет в stderr.
Mar 15 10:05:00 server PHP-FPM[3456]: [warning] ...
Пример 3. Использование пользовательского обработчика с регистрацией shutdown
Для перехвата всех типов ошибок (включая фатальные) используем комбинацию set_error_handler и register_shutdown_function:
<?php
// Полный перехват ошибок
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) return;
error_log("Custom handler: severity=$severity, $message in $file:$line");
});
register_shutdown_function(function() {
$error = error_get_last();
if ($error !== null && $error['type'] === E_ERROR) {
error_log("Fatal error: " . $error['message'] . " in " . $error['file'] . ":" . $error['line']);
}
});
// Вызов ошибки
call_user_func('undefined_function');
?>
Результат: в error_log появится запись о фатальной ошибке, а stderr останется пустым (если log_errors выключен или перенаправлен).
Пример 4. Перенаправление stderr в nginx с помощью fastcgi_intercept_errors
Если нужно, чтобы ошибки из stderr отображались как ответ 500, а не просто в логе, можно включить fastcgi_intercept_errors on. Тогда сообщение 'fastcgi sent in stderr' не появится, а вместо него будет возвращён код 500 (само сообщение не выводится клиенту).
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_intercept_errors on;
error_page 500 /500.html;
}
Проверка: ошибка PHP теперь не пишется ни в лог, ни в stderr - nginx возвращает 500. Для отладки следует оставить лог ошибок (доступен через upstream's response).
Пример 5. Сравнение конфигураций Apache (mod_fastcgi vs mod_proxy_fcgi)
Если используется Apache с mod_fastcgi (устаревший), для увеличения буфера необходимо задать FastCgiBufferSize в конфигурации виртуального хоста:
FastCgiBufferSize 65536
Для mod_proxy_fcgi (современный) директива ProxyFCGIBufferSize доступна в контексте <Proxy>:
<Proxy "fcgi://localhost:9000/">
ProxyFCGIBufferSize 65536
ProxySet timeout=300
</Proxy>
После изменения перезапустите Apache и проверьте лог на наличие 'FastCGI sent in stderr'.