Your WordPress debug.log Is Publicly Accessible — Here's What It's Leaking
· 10 min read
A routine security audit on a client's WooCommerce store turned up something I see far too often: a 1.2GB debug.log file sitting in wp-content, publicly accessible to anyone who knew the URL. No authentication. No server-level block. Just a plain text file containing eighteen months of PHP errors, database connection strings, and — worst of all — session cookies from the LiteSpeed Cache debug logger.
The store had been running WP_DEBUG set to true since a developer had troubleshot a payment gateway issue a year and a half earlier. They'd fixed the payment issue, committed the config change to their deployment script, and never toggled it back. Every PHP notice, warning, and fatal error had been silently appending to that file ever since.
What Was Actually in the Log
I pulled a copy of the file to a secure local directory and started checking what had been exposed. The contents were worse than I expected.
Database credentials in connection errors. During two brief MariaDB outages, WordPress had logged connection failures that included the database host, username, and database name in the error string:
[15-Mar-2025 03:42:19 UTC] WordPress database error: Access denied for user 'woo_prod'@'10.0.1.14' (using password: YES)
That's a username and an internal IP address, handed to anyone who fetches the file.
Full file paths in PHP warnings. Every deprecation notice and undefined index warning included absolute server paths:
PHP Warning: Undefined array key "billing_phone" in /home/clientsite/htdocs/wp-content/plugins/woocommerce/includes/class-wc-checkout.php on line 1047
That tells an attacker the exact server layout, the hosting panel in use, and which plugin versions are installed — all useful for targeting known vulnerabilities.
Session cookies logged by LiteSpeed Cache. This was the critical finding. The site used LiteSpeed Cache with its debug logging enabled. Versions before 6.5.0.1 had a vulnerability (CVE-2024-44000, CVSS 9.8) where the plugin logged HTTP response headers — including Set-Cookie headers — to debug.log. That meant valid wordpress_logged_in_* session cookies for admin users were sitting in the file in plain text.
[10-Feb-2025 14:22:03 UTC] [LiteSpeed] Set-Cookie: wordpress_logged_in_abc123def456=admin%7C1707654321%7CrandomTokenHere; path=/; secure; HttpOnly
Anyone who grabbed that cookie value before it expired could have pasted it into their browser and been logged in as the site administrator. Full access to the WordPress dashboard, WooCommerce orders, customer data — everything.
How Easily Attackers Find These Files
This isn't theoretical. Automated scanners check https://example.com/wp-content/debug.log on every WordPress site they crawl. It's one of the first paths that vulnerability scanners like WPScan and Nuclei test. If the file exists and returns a 200 status code, the scanner flags it and moves on to extract whatever it can.
Search engines index them too. A search for inurl:"/wp-content/debug.log" returns thousands of results — live debug logs from production WordPress sites, publicly cached and searchable. If your debug.log was exposed for any length of time, there's a reasonable chance it was downloaded by at least one automated tool.
I confirmed the client's file was reachable with a simple curl:
curl -s -o /dev/null -w "%{http_code}" https://clientsite.com/wp-content/debug.log
The response was 200. No redirect, no authentication challenge, no 403. Just the raw file.
Immediate Response
Once I confirmed the exposure, I treated it as a security incident, not a configuration fix.
Step 1: Block access immediately. On nginx, I added a location block to deny access to all log files under wp-content:
location ~* /wp-content/.*\.log$ {
deny all;
return 403;
}
For Apache/.htaccess, the equivalent:
Require all denied
I reloaded nginx and confirmed the file now returned 403.
Step 2: Delete the exposed file. The log had been publicly accessible for months. Keeping it on the server served no purpose — any useful debugging information was long stale. I moved it off the server, then removed it:
scp user@server:/home/clientsite/htdocs/wp-content/debug.log /secure/local/path/
ssh user@server "rm /home/clientsite/htdocs/wp-content/debug.log"
Step 3: Disable debug mode. I turned off WP_DEBUG in wp-config.php via WP-CLI to stop new data being written:
wp config set WP_DEBUG false --raw
wp config set WP_DEBUG_LOG false --raw
wp config set WP_DEBUG_DISPLAY false --raw
Step 4: Rotate credentials. Because database connection details and session cookies had been exposed, I treated them as compromised:
- Changed the MySQL/MariaDB password for the
woo_produser - Updated
DB_PASSWORDinwp-config.php - Regenerated the WordPress salts to invalidate all existing session cookies:
wp config shuffle-salts
- Revoked all active admin sessions:
wp user list --field=ID | xargs -n 1 wp user session destroy --all
- Rotated API keys for payment gateways and any third-party services whose credentials might have appeared in error messages
Hardening: Making debug.log Safe for When You Need It
Disabling debug mode permanently isn't realistic. Debugging is a legitimate need — you just need to do it safely on production. Here's the configuration I now deploy on every site I manage.
Move the log outside the web root
Since WordPress 5.1, WP_DEBUG_LOG accepts an absolute path. Put the log somewhere the web server can't serve:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/var/log/wordpress/clientsite-debug.log' );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
Create the directory with proper ownership:
sudo mkdir -p /var/log/wordpress
sudo chown www-data:www-data /var/log/wordpress
sudo chmod 750 /var/log/wordpress
Now even if a misconfiguration exposes wp-content, the debug log isn't in there.
Set up logrotate
A debug log that grows indefinitely will eat your disk eventually. I wrote about a PHP 8.4 deprecation flood that generated 14GB of log data in three days. Logrotate prevents that:
/var/log/wordpress/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 640 www-data www-data
su www-data www-data
}
Save that to /etc/logrotate.d/wordpress. Test it with:
sudo logrotate -d /etc/logrotate.d/wordpress
The -d flag does a dry run so you can verify the configuration without actually rotating anything.
Block log files at the server level
Even with the debug log moved outside the web root, I still block access to any .log file under the WordPress installation. Defence in depth — plugins can create their own log files in wp-content, and some do it in publicly accessible locations.
nginx:
location ~* /wp-content/.*\.(log|txt|md)$ {
deny all;
return 403;
}
Apache (.htaccess in wp-content):
Require all denied
Audit across all managed sites with WP-CLI
After this incident, I ran an audit across every site I manage to check for exposed debug logs. This one-liner checks the debug configuration on a single site:
wp config get WP_DEBUG 2>/dev/null && wp config get WP_DEBUG_LOG 2>/dev/null
For bulk checking across multiple sites, I scripted it to iterate over every WordPress installation on the server:
for dir in /home/*/htdocs; do
if [ -f "$dir/wp-config.php" ]; then
site=$(basename $(dirname "$dir"))
logfile="$dir/wp-content/debug.log"
if wp config is-true WP_DEBUG --path="$dir" 2>/dev/null; then
echo "WARNING: WP_DEBUG enabled on $site"
fi
if [ -f "$logfile" ]; then
size=$(du -sh "$logfile" | cut -f1)
echo "WARNING: debug.log exists on $site ($size)"
fi
fi
done
That audit found three other sites with WP_DEBUG enabled and debug.log files ranging from 80MB to 400MB. None had been intentionally left on.
Monitoring Going Forward
I added two checks to the monitoring stack I run for all managed sites:
HTTP check for exposed log files. A simple cron job that curls the debug.log path on every site and alerts if it gets anything other than a 403 or 404:
status=$(curl -s -o /dev/null -w "%{http_code}" "https://clientsite.com/wp-content/debug.log")
if [ "$status" != "403" ] && [ "$status" != "404" ]; then
echo "ALERT: debug.log accessible on clientsite.com (HTTP $status)"
fi
File size monitoring. Even when the log is outside the web root, an unchecked log file is a disk space incident waiting to happen. I set a threshold alert at 50MB.
The Real Lesson
The debug.log problem isn't a one-off misconfiguration. It's a process failure. A developer enables debugging to fix an issue, fixes the issue, and forgets to disable debugging. It happens on every site sooner or later if there's no guardrail.
The fix isn't "remember to turn it off" — that's a human reliability problem, and humans aren't reliable. The fix is structural:
- Never let
WP_DEBUG_LOGwrite to the default location. Always set an absolute path outside the web root. - Block
.logfiles at the web server level regardless. - Use logrotate so forgotten logs can't fill your disk.
- Monitor for exposed files automatically.
If you're managing more than a handful of WordPress sites, this is the kind of thing that's easy to miss until it becomes a breach. It's one of the first things I check on every site that comes under a maintenance plan.
Stop Firefighting. Start Maintaining.
I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.
