A blank page, a 500 error, or “something went wrong”: the real reason is almost always in the PHP error log. The tricky part is knowing where PHP writes it, because that depends on the SAPI (PHP-FPM, Apache module, CLI), the pool configuration, and php.ini. Here’s how to find and configure it.

Quick: where are errors going right now?

php -i | grep -E '^(error_log|log_errors|display_errors) '           # CLI settings
sudo php-fpm8.3 -tt 2>&1 | grep -iE 'error_log|catch_workers_output'  # FPM pool config dump

From inside the web app (a temporary file you delete afterwards):

<?php echo ini_get('error_log') ?: '(not set: goes to the SAPI / web server log)';

Typical locations

Setup Where PHP errors appear
Nginx + PHP-FPM (Debian/Ubuntu) Nginx error log (/var/log/nginx/error.log) as “FastCGI sent in stderr”, and/or the FPM log /var/log/php8.3-fpm.log
PHP-FPM with error_log set The file you configured (e.g. /var/log/php/example.log)
Apache + mod_php or FPM /var/log/apache2/error.log (or the vhost’s ErrorLog)
RHEL family PHP-FPM /var/log/php-fpm/www-error.log (pool php_admin_value[error_log])
WordPress with WP_DEBUG_LOG wp-content/debug.log. See WordPress debug mode
Laravel storage/logs/laravel.log (application exceptions)
CLI scripts / cron stderr, or the error_log from the CLI php.ini

Log errors to a file, and don’t display them to visitors (which leaks paths, queries and sometimes credentials):

; /etc/php/8.3/fpm/conf.d/99-errors.ini
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT

Per site, set the log file in the PHP-FPM pool:

; /etc/php/8.3/fpm/pool.d/example.conf
php_admin_value[error_log] = /var/log/php/example-error.log
php_admin_flag[log_errors] = on
catch_workers_output = yes
sudo mkdir -p /var/log/php && sudo chown example:example /var/log/php    # writable by the pool user
sudo systemctl reload php8.3-fpm

Per-site logs make it much easier to find which application failed on servers hosting several sites. See PHP-FPM tuning and php.ini settings.

Read the log

sudo tail -f /var/log/php/example-error.log
sudo grep -i "fatal" /var/log/php/example-error.log | tail

Common messages:

Message Meaning
PHP Fatal error: Allowed memory size … exhausted Raise memory_limit, or fix a leak
Maximum execution time of 30 seconds exceeded Slow code or external calls
Call to undefined function … Missing extension, or incompatible code. See PHP extensions
Uncaught Error: Class "…" not found Autoload or Composer issue, or missing files
PHP Warning: … Permission denied File ownership. See WordPress file permissions
server reached pm.max_children (FPM log) Pool too small, or slow requests

Slow requests: the FPM slow log

Find what PHP is doing in slow requests:

request_slowlog_timeout = 5s
slowlog = /var/log/php/example-slow.log

The slow log records a stack trace of any request taking longer than 5 seconds.

Log rotation

PHP doesn’t rotate its own logs. Add a logrotate rule:

# /etc/logrotate.d/php-sites
/var/log/php/*.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
    copytruncate
}

See logrotate.

Centralise for several servers

Ship PHP and web server logs to a central system (Loki, OpenSearch), and alert on spikes of fatal errors. See log management. For application-level exception tracking with context (user, request, stack trace), use an error tracker. See error tracking with Sentry.

Quick reference

Task Setting / command
Log, don’t display log_errors=On, display_errors=Off
Per-site file php_admin_value[error_log] in the pool
Find current setting php -i | grep error_log
Live view tail -f /var/log/php/site-error.log
Slow requests request_slowlog_timeout, slowlog
Rotate logrotate with copytruncate

Related: Nginx access and error logs and WordPress 500 error.

  • PHP
  • Logs
  • Troubleshooting