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 |
Recommended production configuration
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.