A debug log on a live client site, and how to end it

15/10/2026 · Laurent WordPress in production
A debug log on a live client site, and how to end it

Enabling debug logging is one constant and it is the easy part. Living with it on a site that is serving real visitors is where the three problems are, and all three are operational rather than technical.

The file is in a directory the web server will happily serve. It grows in proportion to traffic rather than to how broken the site is. And it stays on, because turning it off is nobody's task.

The file is public until you move it

The default location is inside the content directory, which means it has a URL. Anyone who guesses it reads your stack traces, your absolute paths, your table prefix, and whatever a badly written plugin decided to include in an error message.

Check before assuming your host blocks it:

curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/wp-content/debug.log

A 200 is a finding. Give the constant an explicit path outside the document root instead of a boolean:

define( 'WP_DEBUG_LOG', '/home/client/logs/wp-errors.log' );

Where there is no directory outside the root, which happens on the most restrictive shared plans, put it somewhere unguessable and deny it in the server configuration. That is a weaker answer and it is better than the default.

The noise, which is worse than the volume

Turn debugging on for a site running any plugin written a few PHP versions ago and the log fills with deprecation notices. Thousands of identical lines, one set per request, describing things that are not the bug you are chasing.

Two costs. Finding your actual error in there is genuinely hard. And on a busy site the write volume is significant in itself, because every one of those notices is a disk write in the request path.

Narrowing what gets recorded is not done in wp-config.php, and this catches people. WordPress sets the reporting level itself while loading, after the configuration file has already run, so anything you set there is overwritten. The place that works is a must-use plugin, which loads later:

<?php
// wp-content/mu-plugins/00-log-level.php
error_reporting( E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED & ~E_NOTICE );

That leaves warnings and fatals, which is what you were looking for. Remove the file when you are done, and give it a name that sorts first so you can see it in a directory listing.

The file that fills the account

A log growing at a few hundred bytes per request, on a site with real traffic, reaches gigabytes in weeks. On shared hosting the account has a disk quota, and hitting it does not produce a message about logging. It produces upload failures, database write errors, and a site that breaks in ways completely unrelated to the bug you were investigating.

If you have root, use the system's rotation. If you do not, which is the usual case, put the guard in the same crontab entry that runs the site's scheduled tasks:

0 * * * * f=/home/client/logs/wp-errors.log; \
  [ -f "$f" ] && [ "$(stat -c%s "$f")" -gt 52428800 ] && : > "$f"

Truncating rather than deleting matters: PHP holds the file open, and removing it means subsequent writes go to a file nobody can see until the process restarts.

Fifty megabytes is an arbitrary threshold and it is roughly the point past which nobody is going to read the file anyway.

Logging one thing instead of everything

Global debugging is a blunt instrument for a specific question. When you already know roughly where the problem is, a temporary must-use plugin that records exactly the condition you care about produces ten lines instead of ten thousand.

<?php
// wp-content/mu-plugins/00-trace-checkout.php
add_action( 'woocommerce_checkout_order_processed', function ( $order_id ) {
    error_log( sprintf( '[trace] order %d memory %s',
        $order_id, size_format( memory_get_peak_usage( true ) ) ) );
} );

This is also the only practical approach for a problem that happens once a day on a busy site, where leaving full debugging on for twenty-four hours produces a file too large to open.

Peak memory is worth including in almost any trace of this kind, because it turns a vague "it fails sometimes" into a number you can compare against the ceiling: the lowest of the three limits is the one that applies.

Ending it

The realistic outcome of enabling debug logging on a client site is that it is still on in two years. Not because anyone decided to leave it, but because the bug was fixed, everybody moved on, and no task existed for the cleanup.

Two habits fix this and both are trivially cheap. Put the removal in your task list with a date, at the same moment you add the constant, before you start investigating. And write the date into the configuration itself, so the next person to read the file knows this was temporary:

// 2027-03-04, enabled while investigating checkout fatals. Remove after.
define( 'WP_DEBUG_LOG', '/home/client/logs/wp-errors.log' );

The comment survives handovers, which is more than can be said for the task list.

Debugging is not monitoring

Worth separating, because a debug log is often left on in the belief that it constitutes visibility.

It does not. A log is read when somebody goes to read it, and nobody goes to read the logs of a site that appears to be working. Its entire value is during an investigation you are already conducting.

What you want permanently is different: fatal errors captured and sent somewhere, and an assertion from outside that the pages still contain what they should. The first catches the break, the second catches the breaks that produce a perfectly valid empty page: a blank screen at 200 does not register as a failure anywhere.

Back to contents

Share this post.
Stay up-to-date

Subscribe to our newsletter

Don't miss this

You might also like