Is Your WordPress Debug Log Exposed Right Now?.

WP_DEBUG_LOG writes to a file inside the web root. We requested it as an anonymous visitor and got 200, with server paths and plugin names.

Aditya Sharma·4 min read

Short answer: if WP_DEBUG_LOG is set to true, WordPress writes errors to wp-content/debug.log, which sits inside the web root and is served like any other file. A wordpress debug log exposed this way needs no exploit to read. You request it, and the server hands it over.

Ten seconds to check:

curl -sI https://example.com/wp-content/debug.log

403 or 404 is fine. 200 is not.

What a WordPress debug log exposed to the internet gives away

I turned debugging on for a site, generated ordinary errors, and requested the file as an anonymous visitor.

Terminal output showing a curl request to wp-content/debug.log returning HTTP 200 with 374 bytes, disclosing a PHP notice containing an absolute filesystem path, a theme name, a plugin path and a line number
200 OK. Three disclosures in a single line: the absolute filesystem path of the

install, the names of a theme and a plugin, and a file and line number where the code is already known to misbehave.*

No authentication. No clever request. The file is inside the web root, and the web server has no reason to refuse it.

Table ranking what a debug log discloses. Absolute paths, plugin and theme versions, and database errors are high value. API keys logged by integrations and customer data in payment errors are marked critical
The bottom two rows are why this is not simply untidy. A log file’s contents are

decided by whatever code happened to fail, which means the worst-behaved plugin on the site decides what ends up public.*

That last point is the one I would push back on if someone told me their debug log was harmless. You do not control what goes in it. An integration that catches an exception and logs the whole request will write an API key into a world-readable file, and nothing warns you.

Why it is on in the first place

Nobody enables debug logging on production out of carelessness. It gets enabled during an incident, when somebody needed to see what was failing, and then the incident ends.

The log is the only artefact of that afternoon nobody thinks to clean up.

It is also worth checking after any performance work, because chasing a slow server response is one of the most common reasons somebody switches debugging on in the first place.

On inherited sites I find it more often than any other single misconfiguration, and it is almost always dated. A file that started in March and has been quietly growing since, on a site that has changed hands twice.

Fixing it

Three-step fix table. Move the log outside the web root by passing a path to WP_DEBUG_LOG, deny the debug.log and error_log patterns at the web server, and set WP_DEBUG_DISPLAY false with display_errors off
WP_DEBUG_LOG accepts a path, not just true. That single change moves the file

somewhere the web server cannot serve, and it is the fix that survives somebody turning debugging back on next month.*

The detail worth knowing: logging and displaying are separate decisions. WP_DEBUG_DISPLAY controls whether errors are printed into the page for visitors to read. On production it should be false regardless of what the log setting is doing.

And if the file was public for a while, treat whatever it contained as disclosed. Rotate any key an integration might have logged. Changing file permissions today says nothing about who read it in March.

Check the whole portfolio, not one site

The check is a single request, so run it across everything at once:

for d in clientone.com clienttwo.com clientthree.com; do
  printf '%-28s ' "$d"
  curl -s -o /dev/null -w '%{http_code}  %{size_download} bytes\n' \
    "https://$d/wp-content/debug.log"
done

Watch the size column as well as the status. Some hosts return a styled 200 error page for missing files, so a 200 with a few hundred bytes of HTML is not the same as a 200 with a log file behind it.

While you are there, the same loop is worth pointing at error_log, and at the wp-config backup files that leak the same way. Both are created by the same habit: somebody needed a file for an afternoon.

Protuno’s free audit checks for an exposed debug log from the domain alone, alongside directory listing and the other files that should never answer. Straight with you as on every post: Rook, the Security agent that would keep re-checking after each incident, is built and named but not live yet.

Run the loop above now. It takes longer to read this paragraph than to check twenty sites, and in my experience roughly one in twenty comes back with a log file. It is the same quiet class of problem as a scheduled job that stopped months ago.

Comments