How to Find the Real Error Behind a Broken WordPress Site with WP_DEBUG
Turn on WP_DEBUG, WP_DEBUG_LOG and SAVEQUERIES the safe way, read wp-content/debug.log, and pinpoint whether a plugin, theme, or query is at fault.
The problem: WordPress hides its errors on purpose
A WordPress site fails and the browser gives you almost nothing. A blank page, a generic “There has been a critical error on this website” message, a widget that silently stops loading, or an admin screen that renders half its markup and gives up. The failing PHP code would happily tell you exactly what went wrong — which file, which line, which function — but WordPress suppresses it because error output printed into a live page is both ugly and a security leak.
So the first move in almost every WordPress debugging session is the same: temporarily switch the error reporting on, capture it somewhere you can read it, and then work from the actual error instead of guessing. WordPress ships this mechanism built in, controlled by a handful of constants in wp-config.php. This article covers which constants matter, how to enable them without wrecking your live site, how to read what they produce, and how to narrow the result down to the plugin, theme, or database query responsible.
If you already know the failure is server-level rather than PHP-level — a 500 status before WordPress even loads, for instance — start with our guide to fixing WordPress 500 errors instead. The techniques below apply once WordPress itself is running but something inside it is misbehaving.
The three constants that matter first
All of these live in wp-config.php, in the root of your WordPress directory, above the line that says /* That's all, stop editing! */. WordPress’s Advanced Administration Handbook documents these constants in its Debugging in WordPress article, and it is worth internalizing one rule from it early: quoted booleans are a trap. define( 'WP_DEBUG', 'false' ); evaluates as true, because any non-empty string is truthy in PHP. Write bare true and false.
WP_DEBUG. The master switch. When set to true, it raises PHP’s error reporting level to show all errors, notices, and warnings, and additionally emits notices when deprecated WordPress functions or arguments are used. Most modern plugins are written to run clean under it, so a flood of deprecation notices from one specific plugin is itself diagnostic information: that plugin is aging out of the current WordPress API.
WP_DEBUG_LOG. Writes every error the reporting layer captures into a log file, defaulting to wp-content/debug.log. This is the constant that makes debugging a live site feasible at all, because it decouples capturing the error from displaying it. It is also the only practical way to see errors that occur outside a normal page request — an AJAX call whose response you can’t inspect, or a wp-cron task that fails silently in the background, which is exactly the failure mode behind missed schedule and failed cron tasks.
WP_DEBUG_DISPLAY. Controls whether errors are also printed into the page HTML. It defaults to true, which is what you want on a local development install and almost never what you want on production. Paired with a log file, setting it to false is the standard combination: capture everything, show visitors nothing.
A practical block for a live site under active investigation looks like this:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
The handbook makes two further points worth acting on. First, the log path is customizable — define( 'WP_DEBUG_LOG', '/absolute/path/outside/webroot/debug.log' ); writes somewhere a URL cannot reach, and if you keep the log inside wp-content/, you should restrict its file permissions and block HTTP access to it in your server configuration. Second, the sibling constants do nothing unless WP_DEBUG itself is true. If you enable only WP_DEBUG_LOG and see an empty log, the master switch is still off.
Two more constants for specific situations
SAVEQUERIES stores every database query WordPress runs, with execution time and the calling function, into the global $wpdb->queries array. It answers a different class of question: not “what broke” but “why is this page slow.” The handbook is blunt about the cost — it has a measurable performance impact — so treat it as a short-lived toggle on a staging copy, not a permanent fixture. If a page issues 900 queries where 60 would do, the calling-function list tells you which plugin is responsible, which is far more actionable than a total query count.
SCRIPT_DEBUG forces WordPress to load the unminified versions of core CSS and JavaScript. Reach for it only when the symptom is front-end: styles behaving as though a file is missing, or a JavaScript error that only appears in the compressed build. Ninety percent of debugging sessions never need it.
Reading the log: turn a stack trace into a culprit
Once enabled, tail the log while reproducing the problem:
tail -f wp-content/debug.log
Reproduce the failing action once — load the page, submit the form, trigger the cron task — and read what appears. A typical entry names the error type, the message, and the file path with a line number:
PHP Fatal error: Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array, string given in
/var/www/site/wp-content/plugins/some-plugin/includes/render.php:142
The path is the whole diagnosis. wp-content/plugins/ means a plugin; wp-content/themes/your-theme/ means the theme; wp-includes/ or wp-admin/ means WordPress core was handed bad data — and in that case, the caller listed further down the trace is usually the real author of the bad data, not core itself. Deprecation notices (“Function xyz is deprecated since version 6.x”) follow the same rule: the path identifies who needs updating.
If the log stays empty while the failure reproduces, that is a finding, not a dead end. It usually means the failure happens before WordPress loads its error handling — a fatal parse error in wp-config.php or a plugin file loaded very early — and belongs to the server-level diagnosis described in our 500 error guide. In that case check the web server’s own error log next. On the database side, connection-level failures have their own signature and their own dedicated troubleshooting path.
Narrowing it down when the log points at core
When the stack trace ends in wp-includes/ but you suspect an extension, isolate by subtraction. If you have WP-CLI available, deactivate all plugins in one command and reactivate one at a time — the workflow we describe in updating WordPress safely with WP-CLI — or, without shell access, rename wp-content/plugins to plugins.hold via SFTP and let WordPress disable them all at once, then restore the folder and re-enable individually. Rename, don’t delete; the goal is a reversible experiment.
WP-CLI can also short-circuit the guessing entirely. The wp eval command executes arbitrary PHP with WordPress fully loaded, so you can confirm a suspicion in seconds:
wp eval 'var_dump( function_exists( "some_function" ) );'
wp eval 'echo WP_CONTENT_DIR;'
The command’s own --debug global parameter additionally shows all PHP errors and adds verbosity to WP-CLI’s output, which pairs well with an active WP_DEBUG_LOG when the failure only appears on the command line.
Safety on a live site
The handbook’s guidance is unambiguous: these tools are meant for local and staging environments, not production. The realistic middle ground, when the bug only reproduces in production data, is to follow a proper staging workflow and mirror the site there first. If you must debug live, keep the window short and the display off:
- Enable
WP_DEBUGandWP_DEBUG_LOGwith display disabled, ideally logging outside the web root. - Reproduce the problem once. Don’t leave the flags on overnight.
- Revert
wp-config.phpand remove or secure the log file immediately after. - Verify the site still passes a normal page load once the constants are back to
false.
The reason for the discipline is not just the notices printed on screen. A debug.log left world-readable in wp-content/ records file paths, plugin versions, and sometimes query fragments — reconnaissance material you should not hand out. And a site running with SAVEQUERIES on will run measurably slower, which can confuse a later performance investigation.
When to fix, and when to call it in
Once the log names the culprit, the response is usually straightforward: a plugin with fatal errors gets updated or replaced; a theme function using a deprecated API gets patched in a child theme rather than edited in place, using the same safe-update discipline we describe in updating WordPress safely; a query explosion gets reported to the plugin author with the $wpdb->queries excerpt attached. The one response to avoid is editing the plugin directly — updates will overwrite the fix, and you will have converted one bug into two.
If you would rather not run this loop on a business site yourself, our WordPress development service handles diagnosis and repair as a matter of course, and the WordPress platform overview covers what we build and maintain on it. But the technique itself belongs in every site owner’s toolbox: half of all “my site is broken” reports are solved by turning on the log, reproducing once, and reading the file path it hands you.
Official sources.
Use these references to verify platform-specific behavior and version details.
Continue with the surrounding work.
How to Troubleshoot a WordPress 500 Internal Server Error
How to Fix WordPress Missed Schedules and WP-Cron Problems
How to Fix the WordPress Media Upload HTTP Error for Good
How to Fix WordPress Emails That Are Not Being Sent
Broader context from the YCTheme blog.
Staging Environments: How Professional Teams Ship CMS Changes Without Breaking Production
Staging is where risky changes prove themselves before customers see them. A practical look at how WordPress, Joomla, TYPO3 and Shopify teams stage, test and promote changes.
Headless vs Monolithic CMS: When Decoupling Helps and When It Just Adds Work
A practical comparison of headless and traditional CMS architectures across WordPress, Joomla, TYPO3 and Shopify, focused on editor experience, cost and maintenance.