Guide

WordPress 500 internal server error — how to find the cause in logs

You open your WordPress site and the browser shows nothing but a sterile message: > 500 Internal Server Error No stack trace. No plugin name. No clue. The admin dashboard is gone too — /wp-admin returns the same blank 500.

1. Problem

You open your WordPress site and the browser shows nothing but a sterile message:

500 Internal Server Error

No stack trace. No plugin name. No clue. The admin dashboard is gone too — /wp-admin returns the same blank 500. Five minutes ago everything worked. You did not deploy. You did not edit a file. Maybe a plugin auto-updated. Maybe a cron job ran. Maybe the host hiccupped. You do not know, and the page will not tell you.

This is the most common form of a WordPress 500 internal server error: a silent, total failure that hides the cause behind a generic web server response. Refreshing does nothing. Clearing cache does nothing. The site is down for every visitor, every search engine crawler, and every checkout flow you depend on. If you came here Googling "wordpress 500 internal server error fix" or "wordpress 500 after plugin update", you already know the dashboards will not save you. The answer is in the logs, and this guide walks you through finding it without guessing.

2. Impact

A 500 is not a yellow warning. It is a hard stop. The server told the browser, "I tried to render this and failed so badly I have nothing useful to send back." Every consequence flows from that:

  • Total downtime. The home page, the checkout, the login screen, the REST API — all return 500. WooCommerce stores stop taking orders. Membership sites lock out paying users. Forms fail silently.
  • Revenue loss compounds quickly. Visitors do not retry. They leave, they buy elsewhere, and they do not come back. A two-hour 500 on a transactional site can erase a week of marketing spend.
  • SEO damage. Googlebot interprets sustained 5xx responses as a signal the site is broken. Recrawl pressure drops. Rankings can take days to recover even after the fix.
  • Hidden security risk. A 500 is sometimes the first visible symptom of a deeper problem — a compromised plugin file, a corrupted core file, or an exploit attempt that pushed memory past the limit. Treating the 500 as the problem instead of the symptom can leave the actual breach untouched.

The fix is rarely the hard part. The hard part is knowing which of fifty plugins, two themes, and one PHP runtime caused it.

3. Why It’s Hard to Spot

WordPress site owners chronically miss 500 errors for the same handful of reasons:

  • The dashboard is gone. A serious 500 takes down /wp-admin along with the public site. You cannot log in to see what is broken from the very interface you would normally use to troubleshoot.
  • Uptime checkers report binary state, not cause. Pingdom, UptimeRobot, and similar services report "site is down" and stop there. They do not tell you whether it is a 500, a DNS failure, or a TLS expiry — and they certainly do not show the PHP fatal that caused it.
  • Hosting provider dashboards lag. Most managed hosts surface PHP errors only in a downloadable log file, often delayed by minutes. By the time the log appears in the panel, you have been down for ten minutes already.
  • Errors are lossy. PHP-FPM logs rotate. A noisy plugin can flood error_log and push the actual fatal off the bottom of the file before you find it.
  • Auto-updates erase the obvious cause. WordPress core, plugins, and themes can all auto-update silently. If a plugin updated at 04:13 and the site started 500'ing at 04:14, you will only see the link if you correlate the timestamps yourself.

The result is a failure mode where the symptom is loud (the site is down) but the cause is buried in a file you do not normally look at, on a server you may not have shell access to.

4. Cause

A 500 internal server error in WordPress is the surface — the HTTP layer telling the browser the request failed. Underneath, three things tend to be true at once:

  1. PHP raised a fatal error and stopped executing. The PHP-FPM worker (or mod_php process) hit something it could not recover from: an undefined function, a class not found, a memory exhaustion, a stack overflow. PHP wrote nothing to the response body and exited.
  2. The web server saw an empty or malformed response and synthesized a 500. Nginx or Apache had nothing valid to forward, so it returned a generic 500 Internal Server Error to the client. This is the http.request signal with status in the 5xx range — the primary observable from outside the system.
  3. WordPress's own error handler never ran. WordPress's "white screen of death" recovery only catches certain errors. A truly fatal error during plugin or theme load happens before WordPress is ready to render anything, including its own error page.

In Logystera terms, the primary signal is http.request with status: 500 (or sometimes 502/503 if PHP-FPM itself is down). That signal almost always travels with a php.fatal signal emitted milliseconds earlier, and very often follows a wp.state_change signal from the previous hour — a plugin that auto-updated, a theme that was activated, an option that was rewritten. The 500 is the consequence. The fatal is the cause. The state change is the trigger.

If you only watch http.request 5xx, you see the smoke. You need php.fatal to see the fire.

5. Solution

5.1 Diagnose (logs first)

This is where guessing stops. Every 500 has a fingerprint, and the fingerprint is in three log sources. Hit them in this order.

1. PHP error log — the source of php.fatal

The PHP error log is the primary evidence. Its path depends on the stack:

  • PHP-FPM (most modern hosts): /var/log/php-fpm/error.log or /var/log/php8.2-fpm.log
  • mod_php / Apache: /var/log/apache2/error.log
  • WordPress debug log (if enabled): wp-content/debug.log

Tail it live and look for fatals:

tail -f /var/log/php8.2-fpm.log | grep -i "fatal\|stack trace"

Or scan the last hour for the pattern that produces a php.fatal signal:

grep "PHP Fatal error" /var/log/php8.2-fpm.log | tail -50

A real fatal looks like this:

[27-Apr-2026 09:14:22 UTC] PHP Fatal error:  Uncaught Error: Call to undefined function wc_get_logger() in /var/www/html/wp-content/plugins/some-plugin/includes/class-loader.php:42
Stack trace:
#0 /var/www/html/wp-content/plugins/some-plugin/some-plugin.php(11): SomePlugin\Loader->boot()
#1 /var/www/html/wp-includes/class-wp-hook.php(308): include('...')

Two things to read:

  • The error type and messageCall to undefined function, Allowed memory size of N bytes exhausted, Class 'X' not found, Maximum execution time exceeded. This tells you the failure class.
  • The first non-core stack frame — the topmost path in wp-content/plugins/... or wp-content/themes/.... That is your suspect.

Each pattern maps to a Logystera signal:

  • grep "PHP Fatal error" → produces php.fatal signal
  • grep "Allowed memory size" → produces php.fatal with cause memory_exhausted
  • grep "Maximum execution time" → produces php.fatal with cause timeout

2. Web server access log — the source of http.request 5xx

Confirm the timing and scope of the 5xx burst:

grep ' 500 ' /var/log/nginx/access.log | tail -100
awk '$9 ~ /^5/ {print $4, $7, $9}' /var/log/nginx/access.log | tail -50

This produces http.request events with status in the 5xx range. You are looking for two things:

  • When did 5xx start? Compare against the timestamp of the first php.fatal. They should align within seconds.
  • Which routes 500? If only /wp-admin/admin-ajax.php returns 500, the fatal is in an AJAX handler. If every route 500s including the home page, the fatal is in a globally loaded plugin or theme.

3. Nginx/Apache error log — the bridge

The web server error log shows what the upstream PHP process did or did not return:

grep -E "FastCGI|upstream|primary script unknown" /var/log/nginx/error.log | tail -30

upstream prematurely closed connection confirms PHP-FPM died mid-request, which is the upstream view of a php.fatal you should also see in the PHP log.

4. Correlate with wp.state_change

If you have audit logging in place, search for state changes in the hour before the 500 burst:

-- WordPress audit table (or Logystera signal store)
SELECT created_at, action, object_type, object_name
FROM audit_events
WHERE created_at > NOW() - INTERVAL '2 hours'
  AND action IN ('plugin_activated', 'plugin_updated', 'theme_switched', 'core_updated')
ORDER BY created_at DESC;

Each row here is a wp.state_change signal. A plugin_updated row at 09:13:50 followed by a php.fatal at 09:14:22 is your answer — without ever opening /wp-admin.

5.2 Root Causes

(see root causes inline in 5.3 Fix)

5.3 Fix

The fix depends on which fatal pattern you found in section 5. Walk these in order of likelihood.

Cause 1: Plugin conflict or bad plugin update

By far the most common. A plugin update introduced a call to a function that no longer exists, or referenced a class from another plugin that has not been activated.

  • Signal pattern: php.fatal with "Call to undefined function" or "Class 'X' not found" → http.request 5xx, often correlated with a wp.state_change plugin_updated event in the last hour.
  • Fix: Disable the suspect plugin. Without dashboard access, rename its directory via SSH or SFTP:
  cd /var/www/html/wp-content/plugins
  mv some-plugin some-plugin.disabled

Reload the site. If the 500 is gone, you found it. Roll back to the previous plugin version or wait for a patch.

  • If you do not know which plugin: rename the entire plugins directory to plugins.off, confirm the site loads, then move plugins back one at a time, reloading between each.

Cause 2: Memory exhaustion

A plugin or theme is consuming more memory than memory_limit allows.

  • Signal pattern: php.fatal with "Allowed memory size of N bytes exhausted" → http.request 5xx. Often correlated with a high-traffic burst or a heavy admin operation.
  • Fix: Raise memory_limit in wp-config.php as a temporary mitigation:
  define('WP_MEMORY_LIMIT', '512M');
  define('WP_MAX_MEMORY_LIMIT', '512M');

Then find the offender. The stack trace in the fatal points to the plugin holding memory at the moment it ran out. Raising the limit forever is treating a symptom; identify and fix the leak.

Cause 3: Corrupted core or .htaccess

A failed core update or a malformed rewrite rule can produce a 500 with no PHP fatal at all.

  • Signal pattern: http.request 5xx with no preceding php.fatal; nginx error log shows rewrite or syntax errors.
  • Fix: Restore .htaccess to the WordPress default. If core files are corrupted, re-upload wp-admin/ and wp-includes/ from a fresh WordPress download of the same version (do not touch wp-content/).

Cause 4: PHP version mismatch

A plugin requires PHP 8.1 features but the server runs 7.4 (or vice versa after a host upgrade).

  • Signal pattern: php.fatal with "syntax error, unexpected ..." or "Cannot use ::class on null" → http.request 5xx, frequently right after a host's PHP version was bumped (visible as a wp.state_change if your platform tracks runtime changes).
  • Fix: Roll the PHP version back via your host's panel, or roll the plugin to a compatible version.

Cause 5: Database connection failure

A 500 can hide a "Error establishing a database connection" if the host wraps DB errors.

  • Signal pattern: php.fatal or php.warning mentioning mysqli_real_connect or Connection refusedhttp.request 5xx.
  • Fix: Check DB credentials in wp-config.php. Confirm the DB host is reachable. Check your hosting provider's status page.

5.4 Verify

A reload that works is not enough. The 500 may have been intermittent. Verify by signal absence and signal stability.

  • Watch for php.fatal to stop. Run:
  tail -f /var/log/php8.2-fpm.log | grep "PHP Fatal error"

Under normal traffic, no new fatal lines should appear for at least 30 minutes. A clean log is the strongest signal of recovery.

  • Watch http.request 5xx return to baseline.
  awk '$9 ~ /^5/' /var/log/nginx/access.log | wc -l

The 5xx rate should drop to near-zero within a minute of the fix. A residual handful is acceptable (bots probing dead URLs); a sustained rate above pre-incident baseline means the fix is incomplete.

  • Hit the routes that were broken. Load the home page, /wp-admin, the checkout, and at least one REST endpoint such as /wp-json/wp/v2/posts. All should return 200.
  • Define healthy. Healthy is: zero new php.fatal entries for 30 minutes under normal traffic, http.request 5xx rate at or below pre-incident baseline, and no new wp.state_change events introducing the same plugin version that caused the original fatal.

If php.fatal reappears even once, the fix is wrong or partial. Do not declare victory on a single successful page load.

6. How to Catch This Early

Fixing it is straightforward once you know the cause. The hard part is knowing it happened at all.

This issue surfaces as http.request (status 5xx).

The honest part of this guide: fixing a 500 is mechanical once you have the logs. The hard problem is the gap between "the site is down" and "I am looking at the right log line."

A traditional WordPress monitoring setup will not close that gap. Uptime checkers tell you the status code. The dashboard hides the cause. The PHP error log is on a server you may not check daily, and even when you do, scrolling through thousands of warnings to find one fatal is a poor use of incident time.

This is exactly the failure shape Logystera is built for. The 500 surfaces as http.request with status: 5xx. The cause surfaces as php.fatal with the file path and stack frame. The trigger surfaces as wp.state_change from the plugin auto-update an hour earlier. Logystera detects each of these signals in real time, correlates them by entity and time window, and alerts on the chain rather than the symptom — so the first thing you see is "plugin X updated at 09:13, fatal at 09:14, 5xx burst at 09:14, suspect: plugin X line 42," not "site appears down."

You do not need to grep at 3am if the chain is already on the screen.

7. Related Silent Failures

If a 500 surfaced this problem, these adjacent failures share its signal family and frequently precede or follow it on the same site:

  • WordPress white screen of death (WSOD) — a php.fatal that does not always escalate to a 500, leaving a blank page instead. Same cause, different surface.
  • Allowed memory size exhausted fatals under load — a memory_near_limit warning pattern that escalates into php.fatal and http.request 5xx during traffic spikes.
  • Plugin auto-update breakage — a wp.state_change of type plugin_updated followed within minutes by a php.fatal cluster. The pattern that produced this guide's example.
  • REST API 500s onlyhttp.request 5xx scoped to /wp-json/* paths, usually a php.fatal in a REST controller registered by a plugin.
  • Cron-driven fatalsphp.fatal correlated with wp.cron runs, surfacing only on routes that trigger scheduled events. The site looks fine to humans and breaks for the scheduler.

Each of these is a different angle on the same diagnostic principle: the 500 is the symptom, the fatal is the cause, the state change is the trigger, and the logs are the only place where all three are visible in one timeline.

See what's actually happening in your WordPress system

Connect your site. Logystera starts monitoring within minutes.

Logystera Logystera
Monitoring for WordPress and Drupal sites. Install a plugin or module to catch silent failures — cron stalls, failed emails, login attacks, PHP errors — before users report them.
Company
Copyright © 2026 Logystera. All rights reserved.