Skip to main content
Blog|
How-to guides

PHP Error Logging: How to Log Errors to a File

|
Mar 19, 2026|10 min read
HOW-TO GUIDESPHP Error Logging: How to LogErrors to a FileHOSTNEYhostney.comMarch 19, 2026

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:

DirectiveEffect
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.