When a PHP script fails on a production server, you should not see the error in the browser. Displaying errors to visitors exposes file paths, database details, and other internals that help attackers. But the error still needs to go somewhere. That somewhere is a log file.
PHP error logging writes error messages to a file on disk instead of outputting them to the browser. You can review the log on your own schedule, search it for patterns, and keep a historical record of every problem your application has encountered. This guide covers how to configure PHP error logging, how to write custom log entries, how to read and manage log files, and when logging is the right approach versus displaying errors directly.
For the companion guide on displaying errors in the browser during development, see How to display PHP errors and enable error reporting.
Enabling error logging in php.ini#
Two directives control whether PHP writes errors to a log file:
log_errors
and
error_log
.
log_errors
This is the master switch. It must be
On
for PHP to write errors to a file:
log_errors = On
This is independent of
display_errors
. You can have both on (errors are shown in the browser and written to the log), both off (errors are silently discarded), or any combination. On production servers, the standard configuration is
log_errors = On
and
display_errors = Off
.
error_log
This directive sets the file path where PHP writes error messages:
error_log = /var/log/php/error.log
If
error_log
is not set, PHP falls back to the web server’s error log. For Apache, that is typically
/var/log/apache2/error.log
. For Nginx with PHP-FPM, errors go to the PHP-FPM pool’s error log, usually
/var/log/php-fpm/www-error.log
or similar.
Setting an explicit path is better than relying on the default. It keeps PHP errors separate from web server errors, making both easier to read.
error_reporting level
The
error_reporting
directive controls which error levels are logged:
error_reporting = E_ALL
E_ALL
logs everything: fatal errors, warnings, notices, deprecation warnings. For production, a common setting is:
error_reporting = E_ALL & ~E_DEPRECATED & ~E_NOTICE
This logs all errors and warnings but excludes deprecation notices and minor notices that are informational rather than actionable.
Complete php.ini logging configuration
A typical production configuration:
log_errors = On
error_log = /var/log/php/error.log
error_reporting = E_ALL & ~E_DEPRECATED & ~E_NOTICE
display_errors = Off
log_errors_max_len = 4096
log_errors_max_len
sets the maximum length of a single log entry in bytes. The default is 1024, which truncates long error messages. Increasing it to 4096 avoids losing the end of stack traces.
After changing php.ini, restart PHP-FPM:
sudo systemctl restart php8.2-fpm
Replace
8.2
with your PHP version. To find which php.ini file is active and where it lives on your system, see How to check your PHP version and find php.ini.
File permissions
The log file must be writable by the user that runs PHP. For PHP-FPM, this is typically
www-data
on Ubuntu/Debian or
nginx
/
apache
on CentOS/RHEL.
Create the log directory and set ownership:
sudo mkdir -p /var/log/php
sudo chown www-data:www-data /var/log/php
If PHP cannot write to the log file, errors are silently lost. No error is generated about the logging failure itself. If you suspect logging is not working, check the file permissions first.
Enabling logging at runtime#
If you cannot edit php.ini (common on shared hosting), you can enable logging from within a PHP script:
<?php
ini_set('log_errors', 1);
ini_set('error_log', '/path/to/your/error.log');
error_reporting(E_ALL);
These settings apply only to the current script execution. They do not change the server-wide configuration.
The path must be writable by the PHP process. On shared hosting, a common approach is to log to a file in your home directory:
<?php
ini_set('error_log', __DIR__ . '/php-errors.log');
__DIR__
resolves to the directory containing the current script. This avoids hardcoding an absolute path.
Per-directory configuration with .htaccess
On Apache with mod_php, you can enable logging for an entire directory by adding to
.htaccess
:
php_flag log_errors On
php_value error_log /var/www/html/logs/php-errors.log
This does not work with PHP-FPM (which is the standard setup with Nginx). For PHP-FPM, use php.ini or pool configuration.
Writing to the error log with error_log()#
PHP’s built-in
error_log()
function lets you write custom messages to the log file. This is useful for logging application-level events, debugging values, or recording conditions that are not PHP errors but that you want to track.
Basic usage
<?php
error_log("Payment processing started for order #1234");
This writes to the file defined by the
error_log
php.ini directive. The output format includes a timestamp:
[19-Mar-2026 14:23:45 UTC] Payment processing started for order #1234
Logging variable values
To log the contents of a variable, use
print_r()
or
var_export()
with their return parameter:
<?php
$data = ['user_id' => 42, 'action' => 'checkout', 'items' => 3];
// Using print_r
error_log("Order data: " . print_r($data, true));
// Using var_export (outputs valid PHP syntax)
error_log("Order data: " . var_export($data, true));
The second parameter
true
tells both functions to return the output as a string instead of printing it directly. Without it,
print_r($data)
would output to the browser, which defeats the purpose of logging.
Logging to a specific file
The second and third parameters of
error_log()
let you write to a file other than the default:
<?php
error_log("Custom log entry\n", 3, "/var/log/php/custom.log");
The
3
as the second parameter means “append to file.” The
\n
at the end is necessary because message type 3 does not add a newline or timestamp automatically.
Logging exceptions
When catching exceptions, log the full details:
<?php
try {
$result = riskyOperation();
} catch (Exception $e) {
error_log("Operation failed: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
error_log("Stack trace: " . $e->getTraceAsString());
}
This gives you the error message, the file and line where it occurred, and the full stack trace.
Reading and parsing PHP log files#
Viewing recent entries
To see the last 50 lines of the log:
tail -50 /var/log/php/error.log
To follow the log in real time while reproducing an issue:
tail -f /var/log/php/error.log
New entries appear as they are written. Press Ctrl+C to stop.
Searching for specific errors
Find all fatal errors:
grep "Fatal error" /var/log/php/error.log
Find errors from a specific file:
grep "wp-content/plugins/problematic-plugin" /var/log/php/error.log
Find errors from today:
grep "19-Mar-2026" /var/log/php/error.log
Understanding log entry format
A typical PHP error log entry looks like this:
[19-Mar-2026 14:23:45 UTC] PHP Warning: Undefined variable $config in /var/www/html/wp-content/themes/mytheme/functions.php on line 142
The format is:
- Timestamp in brackets
- Error level (PHP Warning, PHP Fatal error, PHP Notice, etc.)
- Error message describing what went wrong
- File path where the error occurred
- Line number where the error occurred
Stack traces for fatal errors include the call chain:
[19-Mar-2026 14:23:45 UTC] PHP Fatal error: Uncaught Error: Call to undefined function custom_function() in /var/www/html/functions.php:42
Stack trace:
#0 /var/www/html/index.php(15): require_once()
#1 {main}
thrown in /var/www/html/functions.php on line 42
The stack trace reads from bottom to top.
#1 {main}
is where execution started (index.php),
#0
is the file that loaded functions.php, and the final line is where the error actually occurred.
Counting error frequency
To see which errors occur most often:
grep "PHP" /var/log/php/error.log | sed 's/\[.*\] //' | sort | uniq -c | sort -rn | head -20
This strips timestamps, counts unique error messages, and shows the top 20 by frequency. A single error appearing thousands of times is usually a code issue in a loop or a high-traffic page. A variety of different errors suggests broader instability.
Log rotation#
PHP log files grow continuously. On a busy site, the error log can reach hundreds of megabytes or more. Left unchecked, it fills the disk. Log rotation compresses and archives old log entries on a schedule.
logrotate on Linux
Most Linux distributions include logrotate, which handles log rotation automatically. Create a configuration file:
sudo nano /etc/logrotate.d/php-errors
Contents:
/var/log/php/error.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
create 0640 www-data www-data
}
What each directive does:
| Directive | Effect |
|---|---|
weekly
| Rotate the log once per week |
rotate 12
| Keep 12 rotated files (12 weeks of history) |
compress
| Compress rotated files with gzip |
delaycompress
| Wait one rotation cycle before compressing (so the most recent rotated file is uncompressed and easy to read) |
missingok
| Do not error if the log file does not exist |
notifempty
| Do not rotate if the file is empty |
create 0640 www-data www-data
| Create a new empty log file with these permissions and ownership |
After rotation, the active log file is
/var/log/php/error.log
. The previous week’s log is
error.log.1
, the week before that is
error.log.2.gz
, and so on.
Manual log management
If logrotate is not available, you can truncate a log file without deleting it:
> /var/log/php/error.log
This empties the file while keeping the file handle open. PHP continues writing to the same file without interruption. You lose the existing log contents, so copy or archive the file first if you need the history:
cp /var/log/php/error.log /var/log/php/error.log.backup
> /var/log/php/error.log
Do not delete and recreate the log file. If PHP-FPM has the file open and you delete it, PHP continues writing to the deleted file descriptor. The entries go nowhere visible, and disk space is not freed until PHP-FPM is restarted.
WordPress error logging#
WordPress has its own logging mechanism that sits on top of PHP’s error logging.
wp-content/debug.log
When
WP_DEBUG_LOG
is enabled in wp-config.php:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
WordPress writes errors to
wp-content/debug.log
. This is in addition to the PHP error log, not instead of it. If both are enabled, errors appear in both files.
Setting
WP_DEBUG_DISPLAY
to
false
prevents errors from showing in the browser while still logging them. This is the recommended configuration for debugging a production WordPress site.
Custom debug.log location
You can specify a custom path for the WordPress debug log:
define('WP_DEBUG_LOG', '/var/log/wordpress/debug.log');
This is useful if you want WordPress logs in a central location rather than inside the wp-content directory, or if you want to apply logrotate to it.
What gets logged
With
WP_DEBUG
and
WP_DEBUG_LOG
enabled, WordPress logs:
- PHP errors, warnings, and notices from WordPress core, plugins, and themes
- Deprecated function usage (
_deprecated_function()calls in WordPress core) - Database query errors
- HTTP API errors (failed external requests)
It does not log successful operations, page loads, or user activity. It is strictly an error log.
Logging in practice#
Debugging a slow site
When a site is slow and you suspect PHP is the bottleneck, check the error log for repeated warnings. A common pattern is a plugin that generates a warning on every page load. The warning itself is not fatal, but it indicates the plugin is doing something unexpected, and the overhead adds up.
tail -500 /var/log/php/error.log | grep "Warning" | sed 's/\[.*\] //' | sort | uniq -c | sort -rn
If one warning appears hundreds of times in a short period, that plugin needs attention.
Debugging timeout errors
When PHP-FPM workers hit the maximum execution time, the log shows:
[19-Mar-2026 14:23:45 UTC] PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/wp-content/plugins/backup-plugin/backup.php on line 847
This tells you exactly which file and line was executing when time ran out. The fix depends on the situation: the script may need optimization, the execution time limit may need increasing for legitimate long-running operations, or the operation may need to be moved to a background process.
Separating logs by site
If you run multiple sites on one server, each site should have its own log file. In the PHP-FPM pool configuration for each site:
[site1]
php_admin_value[error_log] = /var/log/php/site1-error.log
[site2]
php_admin_value[error_log] = /var/log/php/site2-error.log
This prevents errors from different sites from mixing together. When you are debugging site1, you do not have to filter out site2’s noise.
Logging custom application events
For application-level logging beyond PHP errors, create a simple logging function:
<?php
function app_log($message, $level = 'INFO') {
$timestamp = date('Y-m-d H:i:s');
$entry = "[$timestamp] [$level] $message" . PHP_EOL;
file_put_contents('/var/log/php/app.log', $entry, FILE_APPEND | LOCK_EX);
}
app_log("User 42 started checkout with 3 items");
app_log("Payment gateway returned error: timeout", "ERROR");
Keep application logs separate from PHP error logs. They serve different purposes and have different noise levels.
PHP error logging on Hostney#
On Hostney, PHP error logging is enabled by default. You do not need to configure php.ini directives manually.
PHP logs are available in the control panel under Logs & Statistics > PHP Logs. These show PHP errors, warnings, and notices generated by your site. For web server logs (request logs, Nginx errors), see Logs & Statistics > Error Log. The access and error logs knowledge base page explains what each log type contains and how to interpret the entries.
PHP settings including
error_reporting
,
log_errors
, and
display_errors
are configurable through Hosting > PHP Manager > Variables tab. Changes take effect immediately.