WordPress 500 Internal Server Error: Most Common Causes and a Fast Fix Plan

Was this helpful?

The WordPress 500 error is the web equivalent of your car’s “check engine” light—except the dashboard is a blank page and your boss is watching analytics dip in real time.
The worst part isn’t the error. It’s the vagueness. “Internal Server Error” is basically the server shrugging.

This guide is what you do when you need the site back now and you also want to avoid the fun tradition of “fixing it” by randomly disabling things until it works.
We’ll be surgical: confirm where the 500 is generated, read the right logs, isolate the layer (edge → web server → PHP runtime → WordPress), and ship a fix with a rollback plan.

What a WordPress 500 actually means (and what it doesn’t)

A 500 status code means the server failed to fulfill a request due to an unexpected condition. It does not mean the request was malformed (that’s 400/404),
and it does not mean you’re rate-limited (429) or your upstream is down (502/504). A 500 is usually generated by the origin web server or the application runtime.

In WordPress-land, “500 Internal Server Error” often maps to:

  • Web server configuration errors (bad rewrite rules, invalid directives, missing modules).
  • PHP runtime crashes or fatal errors (uncaught exceptions, undefined functions, memory exhaustion).
  • PHP-FPM pool issues (max children reached, slowlog timeouts, socket permissions).
  • Filesystem permission/ownership problems (can’t read plugins/themes, can’t write cache/temp).
  • Resource constraints (CPU thrash, disk full, inode exhaustion).
  • Security/WAF rules that make upstream throw generic 500s.

Also, don’t forget that “WordPress 500” sometimes isn’t WordPress. It can be the CDN returning a generic 500 because the origin returned something else,
or because the edge can’t reach the origin cleanly. Always verify where the status code is created.

One quote worth keeping on your mental incident checklist comes from Werner Vogels (Amazon CTO): “Everything fails, all the time.” That’s not pessimism; it’s scheduling.

Fast diagnosis playbook (first/second/third checks)

Step 0: Stop guessing. Confirm the failing layer.

Before you touch WordPress, determine whether the 500 is coming from the edge (CDN/WAF), the web server (Nginx/Apache), or PHP (PHP-FPM/mod_php).
Your first win is narrowing the blast radius.

First check (2 minutes): reproduce and capture the response headers

  • If headers show server: cloudflare or similar, validate whether it’s an origin 500 or an edge-generated error.
  • If you see X-Powered-By: PHP and a request ID header from your platform, you have a thread to pull.

Second check (5 minutes): read the right logs, not all logs

  • Web server error log for the timestamp and request path.
  • PHP-FPM error log (or php_errors in syslog/journald).
  • WordPress debug log only if enabled—and preferably enabled temporarily.

Third check (10 minutes): isolate WordPress from the platform

  • Hit a static file (like /favicon.ico) to see if the web server can serve anything.
  • Hit /wp-login.php and a simple PHP info endpoint if you have one (or temporarily create a safe one and remove it).
  • Disable plugins and switch theme (safely) if logs point to PHP fatals or plugin load order issues.

If you do those three checks with discipline, most 500 incidents go from “mysterious” to “actionable” fast.
If you skip them, you’ll do what humans do under stress: change five things and learn nothing.

Most common causes (by layer)

1) CDN/WAF and reverse proxies: origin errors disguised

A CDN can faithfully forward an origin 500, or it can emit its own 500 if it can’t reach the origin, can’t handshake TLS, or gets an invalid response.
Some managed WAFs also block requests and return a branded error page that looks like “500-ish” to users.

Your job: check whether the origin itself returns 500 when accessed directly (bypassing the CDN), and whether the request is being rewritten in a surprising way.

2) Nginx/Apache: rewrite rules, permissions, and misroutes

WordPress depends heavily on rewrites. A single bad directive can turn into a 500. So can an invalid .htaccess line, or an Nginx config referencing a missing file.
In Apache, 500 is also what you get when the server hits a directive it doesn’t understand (often after a module change).

3) PHP runtime: fatal errors, memory, and incompatible versions

Most real WordPress 500s are PHP fatals. A plugin update calls a function that doesn’t exist in your PHP version. A theme assumes an extension is installed.
A memory leak turns into “Allowed memory size exhausted” and the runtime stops mid-request.

Another frequent culprit is opcode caching weirdness after deployment. OPcache is great—until it isn’t. Clearing it can be the difference between stable and haunted.

4) PHP-FPM: pools under pressure

When PHP-FPM hits pm.max_children, requests queue. Depending on your web server timeouts, that often shows as 502/504, but it can also present as 500
if the upstream handling is misconfigured or the app returns partial output. Also: socket permission issues and chroot/open_basedir constraints can turn into failures that look like application errors.

5) Filesystem and storage: disk full, inode exhaustion, and permission drift

WordPress writes. It writes cache files, uploads, plugin updates, temporary files, session files, sometimes logs. If disk is full, you can get surprisingly generic errors.
If inode count is exhausted, you can have gigabytes free and still be effectively “full.”

Permission drift is more common than people admit: one manual edit as root, one deploy that changes ownership, one security hardening step that blocks the PHP user from reading a plugin file.
The web stack responds with—you guessed it—500.

6) Database and external dependencies: errors bubble up badly

Pure database outages tend to show as “Error establishing a database connection,” but depending on error handling and object caching layers,
DB exceptions can get swallowed and re-thrown as generic 500s. Redis/Memcached misconfigurations can do the same.

Joke #1: A 500 error is the server’s way of saying “I have feelings too,” and right now they’re mostly panic.

Practical tasks with commands, outputs, and decisions

These are production-grade tasks. They assume Linux, Nginx or Apache, and PHP-FPM. Adjust paths for your distro.
Every task includes (a) a command, (b) what typical output means, and (c) the decision you make next.

Task 1: Capture the failing response and confirm where the 500 is generated

cr0x@server:~$ curl -sS -D- -o /dev/null https://example.com/ | sed -n '1,20p'
HTTP/2 500
date: Fri, 26 Dec 2025 18:42:10 GMT
content-type: text/html; charset=UTF-8
server: nginx
x-request-id: 7f2c9a4e9b0f3c1a

What it means: The response is coming from nginx (not a CDN header). The request ID is gold if your logs include it.
Decision: Go straight to Nginx error logs and PHP-FPM logs for this timestamp/request ID.

Task 2: Bypass the CDN and hit origin directly (if possible)

cr0x@server:~$ curl -sS -D- -o /dev/null -H "Host: example.com" http://203.0.113.10/ | sed -n '1,20p'
HTTP/1.1 500 Internal Server Error
Server: nginx
Date: Fri, 26 Dec 2025 18:42:13 GMT
Content-Type: text/html

What it means: Origin returns 500 too. This is not “CDN being weird.”
Decision: Troubleshoot origin stack. If origin returns 200 but CDN returns 500, investigate edge rules, TLS, WAF, or origin health checks.

Task 3: Watch the web server error log live while reproducing

cr0x@server:~$ sudo tail -n 50 -f /var/log/nginx/error.log
2025/12/26 18:42:10 [error] 1129#1129: *928 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:1234" while reading response header from upstream, client: 198.51.100.23, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.1-fpm.sock:", host: "example.com"

What it means: Clear root cause: PHP fatal due to missing mbstring extension.
Decision: Install/enable the missing extension, reload PHP-FPM, and retest. Do not touch WordPress plugins yet; the stack is telling you exactly what’s wrong.

Task 4: Check PHP-FPM service health and recent errors

cr0x@server:~$ sudo systemctl status php8.1-fpm --no-pager -l
● php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled)
     Active: active (running) since Fri 2025-12-26 17:01:22 UTC; 1h 40min ago
   Main PID: 901 (php-fpm8.1)
     Status: "Processes active: 12, idle: 4, Requests: 28291, slow: 7, Traffic: 0.0req/sec"
     ...

What it means: Service is running. “slow: 7” hints at slow requests but not necessarily the 500 cause.
Decision: If errors aren’t obvious in web logs, inspect PHP-FPM logs and consider enabling slowlog briefly.

Task 5: Inspect PHP-FPM error log for fatal errors or pool saturation

cr0x@server:~$ sudo tail -n 80 /var/log/php8.1-fpm.log
[26-Dec-2025 18:42:10] WARNING: [pool www] child 2134 said into stderr: "PHP Fatal error:  Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:1234"
[26-Dec-2025 18:40:55] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it

What it means: Two issues: missing extension (immediate 500s) and pool saturation (performance risk).
Decision: Fix the fatal first (restore service), then address capacity: raise pm.max_children only after confirming RAM headroom.

Task 6: Confirm PHP extensions and version align with what WordPress expects

cr0x@server:~$ php -v
PHP 8.1.26 (cli) (built: Nov 21 2025 10:12:33) (NTS)
cr0x@server:~$ php -m | egrep -i 'mbstring|mysqli|curl|gd|imagick|zip|openssl' || true
curl
mysqli
openssl
zip

What it means: No mbstring. WordPress core and many plugins expect it.
Decision: Install php-mbstring package for your distro, restart PHP-FPM, retest. If CLI differs from FPM, verify FPM’s ini path.

Task 7: Validate web server config syntax (cheap, fast, often overlooked)

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

What it means: Nginx config parses.
Decision: If you get errors here, fix them before anything else. No amount of WordPress debugging helps when the web server won’t load rules correctly.

Task 8: For Apache, check config and .htaccess fallout

cr0x@server:~$ sudo apachectl configtest
Syntax OK

What it means: Core Apache config is OK. That doesn’t guarantee .htaccess isn’t breaking requests.
Decision: Temporarily move .htaccess out of the way if you suspect rewrite issues, then retest.

Task 9: Quarantine .htaccess to rule out bad directives

cr0x@server:~$ cd /var/www/html
cr0x@server:~$ sudo mv .htaccess .htaccess.bak.$(date +%s)
cr0x@server:~$ sudo -u www-data php -l index.php
No syntax errors detected in index.php

What it means: If the site recovers after moving .htaccess, your rewrite rules or directives were invalid.
Decision: Recreate permalinks from WordPress admin once stable, or rebuild the minimal standard WordPress rewrite rules and reintroduce changes incrementally.

Task 10: Disable plugins safely without wp-admin (rename directory)

cr0x@server:~$ cd /var/www/html/wp-content
cr0x@server:~$ sudo mv plugins plugins.disabled.$(date +%s)
cr0x@server:~$ ls -1
mu-plugins
plugins.disabled.1766774712
themes
uploads

What it means: WordPress won’t load normal plugins if the directory is missing/renamed. It will still load mu-plugins.
Decision: If 500 disappears, a plugin caused the fatal. Restore directory, then re-enable plugins in chunks or one-by-one to identify the offender.

Task 11: Switch to a default theme (when theme code is suspect)

cr0x@server:~$ cd /var/www/html/wp-content/themes
cr0x@server:~$ sudo mv mytheme mytheme.disabled.$(date +%s)
cr0x@server:~$ ls -1
mytheme.disabled.1766774766
twentytwentyfour
twentytwentythree

What it means: If the active theme is missing, WordPress will fall back to an installed default theme.
Decision: If this fixes 500, the theme is broken or incompatible. Roll back the theme deploy or patch the fatal error.

Task 12: Turn on WordPress debug logging (temporarily and responsibly)

cr0x@server:~$ sudo cp -a /var/www/html/wp-config.php /var/www/html/wp-config.php.bak.$(date +%s)
cr0x@server:~$ sudo grep -n "WP_DEBUG" /var/www/html/wp-config.php || true
cr0x@server:~$ sudo sed -i "s/\/\* That's all, stop editing! Happy publishing. \*\//define('WP_DEBUG', true);\ndefine('WP_DEBUG_LOG', true);\ndefine('WP_DEBUG_DISPLAY', false);\n\n\/\* That's all, stop editing! Happy publishing. \*\//" /var/www/html/wp-config.php
cr0x@server:~$ sudo -u www-data tail -n 30 /var/www/html/wp-content/debug.log
[26-Dec-2025 18:45:03 UTC] PHP Fatal error:  Uncaught Error: Class "WooCommerce" not found in /var/www/html/wp-content/themes/mytheme/functions.php:211

What it means: Theme expects WooCommerce, but plugins are disabled or WooCommerce failed to load.
Decision: Fix dependency assumptions (guard code with class_exists), or restore required plugin once stable. Also: disable debug once done; logs can leak internals.

Task 13: Check disk usage and inode exhaustion (storage problems love 500s)

cr0x@server:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   39G  200M 100% /
cr0x@server:~$ df -i
Filesystem       Inodes   IUsed   IFree IUse% Mounted on
/dev/vda1      2621440 2621401      39  100% /

What it means: You’re out of space and out of inodes. WordPress updates, cache writes, session files—anything can fail unpredictably.
Decision: Free space immediately (logs, old releases, cache), then fix the cause (log rotation, cache policy, uploads growth, partition sizing).

Task 14: Identify giant offenders quickly

cr0x@server:~$ sudo du -xhd1 /var | sort -h
120M	/var/cache
1.4G	/var/log
6.8G	/var/lib
cr0x@server:~$ sudo ls -lhS /var/log | head
-rw-r----- 1 syslog adm  920M Dec 26 18:40 syslog
-rw-r----- 1 root  adm  210M Dec 26 18:15 nginx-error.log

What it means: Logs are eating the disk. That often correlates with a new error loop (like a plugin fatal spamming logs).
Decision: Rotate/truncate logs to recover, but also fix the underlying loop. Otherwise you’ll be back here in an hour.

Task 15: Check file ownership and permissions in WordPress directories

cr0x@server:~$ sudo namei -l /var/www/html/wp-content
f: /var/www/html/wp-content
drwxr-xr-x root     root     /
drwxr-xr-x root     root     var
drwxr-xr-x root     root     www
drwxr-xr-x root     root     html
drwx------ root     root     wp-content

What it means: wp-content is 700 owned by root. PHP running as www-data can’t read/write. Expect 500s and weird admin behavior.
Decision: Fix ownership/permissions to match your deployment model (often www-data readable, limited writable for uploads/cache).

Task 16: Validate PHP-FPM socket permissions and connectivity from Nginx

cr0x@server:~$ ls -l /run/php/php8.1-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 26 17:01 /run/php/php8.1-fpm.sock
cr0x@server:~$ sudo grep -R "fastcgi_pass" -n /etc/nginx/sites-enabled | head
/etc/nginx/sites-enabled/example.conf:42: fastcgi_pass unix:/run/php/php8.1-fpm.sock;

What it means: Socket exists and matches config. If permissions were wrong or path mismatched, Nginx would log upstream connection errors.
Decision: If socket path differs, align Nginx config or PHP-FPM listen directive, then reload services.

Task 17: Check for pool saturation and memory headroom before “just increasing max_children”

cr0x@server:~$ ps -o pid,rss,cmd -C php-fpm8.1 | head
  PID   RSS CMD
  901  17800 php-fpm: master process (/etc/php/8.1/fpm/php-fpm.conf)
 2134 145200 php-fpm: pool www
 2135 152300 php-fpm: pool www
cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:            2048        1530          85          40         432         260
Swap:           1024         950          74

What it means: Workers are ~150MB RSS each. You have little available memory and you’re swapping hard.
Decision: Do not raise pm.max_children yet. Reduce memory usage (plugins, object cache settings), add RAM, or scale out.

Task 18: Find PHP fatal errors in journald (common on systemd-based distros)

cr0x@server:~$ sudo journalctl -u php8.1-fpm -S "10 minutes ago" --no-pager | tail -n 30
Dec 26 18:42:10 server php-fpm8.1[901]: PHP Fatal error:  Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:1234

What it means: Confirms a runtime fatal close to the incident time.
Decision: Fix the missing dependency; if the fatal changes after you fix it, keep iterating—don’t assume there’s only one issue.

Task 19: Test a single PHP file through the web server path

cr0x@server:~$ printf '%s\n' '/dev/null
cr0x@server:~$ curl -sS -D- http://127.0.0.1/health.php | sed -n '1,10p'
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8

OK

What it means: PHP execution through the stack works. So the failure is likely in WordPress code path (plugins/theme/core) rather than FPM connectivity.
Decision: Focus on WordPress and its dependencies; keep the health endpoint temporary and remove it after the incident.

Task 20: Remove the temporary test endpoint (because future you deserves peace)

cr0x@server:~$ sudo rm -f /var/www/html/health.php
cr0x@server:~$ test ! -e /var/www/html/health.php && echo "removed"
removed

What it means: The endpoint is gone.
Decision: If you need ongoing health checks, implement them properly (restricted path, auth, and monitored), not as a lingering incident artifact.

Three corporate-world mini-stories (realistic failures)

Mini-story 1: The incident caused by a wrong assumption

A marketing site ran WordPress behind a CDN and a managed WAF. The team saw a spike in “500 errors” in synthetic monitoring and assumed, reflexively, that WordPress had crashed.
They jumped straight into wp-content, renamed plugins, and even rolled back a theme.

Nothing changed. The error persisted, stubbornly. Meanwhile, because they were editing the origin server during peak traffic, they introduced a permission drift:
a quick scp as root dropped files owned by root into the plugin directory. The site got worse. Now they had intermittent 500s and weird admin failures.

The root cause was upstream: the WAF vendor had pushed a new rule set that treated a common query parameter from their ad tracking as suspicious.
The WAF returned a generic 500 page (not a 403), because “security through ambiguity” is apparently still a product feature.

The fix was boring: bypass CDN/WAF to confirm origin health, then add an exception for the tracking parameter and tune the rule. Then undo the unnecessary origin changes.
The lesson: a 500 is not a confession from WordPress. It’s a symptom. Confirm where it’s generated before you open the surgery kit.

Mini-story 2: The optimization that backfired

A platform team wanted faster page loads and lower CPU. They enabled aggressive full-page caching with a plugin, plus server-side microcaching in Nginx.
On paper: fewer PHP executions, happier database, lower cost. And for a week, it looked great.

Then came a sale campaign. Traffic spiked, and so did login attempts (legitimate and otherwise). The caching setup accidentally cached responses that should never be cached:
fragments of logged-in pages and, worse, occasional redirect loops for users with specific cookies. PHP sessions went wild. The cache keys were wrong.

Under stress, PHP-FPM workers got pinned. Requests piled up. The web server started returning a mix of 500s and 502s depending on which timeout fired first.
The plugin also wrote huge cache files into a directory on the root filesystem. Disk hit 100% and everything became “internal server error,” including unrelated services.

The fix wasn’t “disable caching forever.” It was: constrain cache behavior, set correct cache bypass rules for authenticated users, move cache storage to a sized volume,
enforce disk quotas, and set alerts on inode usage. The optimization wasn’t wrong; the lack of guardrails was.

Mini-story 3: The boring but correct practice that saved the day

A content site ran WordPress with a strict deployment pipeline: immutable releases, configs in version control, and a preflight that ran nginx -t,
a PHP syntax check on custom code, and a smoke test that requested /, /wp-login.php, and a static asset.

An engineer merged a change to Nginx rewrite rules to handle a legacy URL structure. The config passed syntax, but the smoke test failed: homepage returned 500.
The pipeline stopped the deploy automatically and kept the old config live. No incident page. No emergency meeting. Just a failed job and a mildly annoyed engineer.

The error was a subtle one: a rewrite rule referenced a named capture group that didn’t exist in some paths, triggering an internal redirect loop and a 500.
Because the smoke test included the homepage and a couple of representative URLs, it caught it immediately.

The moral is aggressively unsexy: preflight checks and smoke tests are cheaper than heroics. You don’t need perfect testing; you need a few checks that fail loudly.

Common mistakes: symptom → root cause → fix

The fastest way to debug is to recognize patterns. The slowest way is to treat every 500 like it’s brand new.
Here are the mistakes I keep seeing in real systems.

1) “500 only on /wp-admin” → memory exhaustion or plugin admin hooks → raise memory properly and fix the plugin

  • Symptom: Front page loads; wp-admin returns 500.
  • Root cause: Admin pages load more code (plugins, editors, builders). Memory limit too low or a plugin fatal triggers in admin-only hooks.
  • Fix: Check PHP logs for “Allowed memory size exhausted,” set memory in PHP-FPM php.ini (not just wp-config), and update/disable the plugin.

2) “500 after enabling permalinks” → bad rewrite config → restore baseline rules

  • Symptom: Works on ?p=123, fails on pretty URLs.
  • Root cause: Missing mod_rewrite (Apache), wrong Nginx try_files, or broken .htaccess.
  • Fix: Validate server rewrite setup, regenerate permalinks, keep rewrite rules minimal.

3) “500 after updating PHP” → extension mismatch or deprecated code → align extensions and upgrade code

  • Symptom: Site dies right after PHP version change.
  • Root cause: A plugin/theme uses removed/deprecated functions; required extensions aren’t installed for the new PHP version.
  • Fix: Check fatal error logs, install missing modules, roll back PHP if needed, then update plugins/themes for compatibility.

4) “Intermittent 500 under load” → PHP-FPM pool saturation or slow DB → scale or tune, don’t just raise timeouts

  • Symptom: Random 500s during traffic spikes.
  • Root cause: FPM workers maxed, or a DB query stalls and consumes workers. Timeouts cascade.
  • Fix: Measure worker RAM, tune pm.max_children responsibly, add caching/object cache, optimize slow queries, scale out.

5) “500 after a ‘small config change’” → Nginx/Apache reload succeeded but behavior broke → diff configs and revert fast

  • Symptom: No deploy, but someone tweaked config.
  • Root cause: Wrong upstream socket, wrong root path, misordered location blocks, internal redirect loops.
  • Fix: Roll back the config change first, then re-apply with tests. In incidents, reversibility beats cleverness.

6) “500 plus ‘permission denied’ in logs” → ownership drift → fix owner/group and umask

  • Symptom: Errors mention “Permission denied” reading PHP files or writing cache/upload.
  • Root cause: Files copied as root; deploy process inconsistent; hardening changed directory modes.
  • Fix: Restore correct ownership, enforce consistent deploy user, and stop editing production as root unless you enjoy recurring incidents.

Checklists / step-by-step plan

Rapid restoration checklist (aim: 15–30 minutes)

  1. Confirm source of 500: curl headers; bypass CDN if possible.
  2. Check web server error log: look for FastCGI/PHP fatals, permission errors, rewrite loops.
  3. Check PHP-FPM logs: fatals, max_children reached, segfaults, slowlog triggers.
  4. Verify config sanity: nginx -t / apachectl configtest.
  5. Check disk + inodes: full disks create “mystery” failures.
  6. Quarantine plugins: rename wp-content/plugins if logs point to plugin-level errors.
  7. Fallback theme: rename active theme directory if theme is implicated.
  8. Restore known-good: revert last deploy, restore from backup/snapshot if corruption suspected.
  9. Remove temporary debug changes: disable WP_DEBUG and remove test endpoints.

Root-cause and hardening checklist (aim: same day)

  1. Make logs queryable: centralize Nginx/Apache + PHP-FPM logs with request IDs.
  2. Add smoke tests: homepage, wp-login, a representative post, and a static asset.
  3. Lock down deployment: consistent ownership, no ad-hoc root edits, immutable release directories if possible.
  4. Monitor the right things: disk %, inode %, PHP-FPM queue/children, 5xx rate, DB latency.
  5. Define rollback: one command or one button. If rollback takes a meeting, it’s not a rollback.
  6. Audit plugins: remove abandoned plugins, pin versions, stage updates.

Joke #2: The only thing more permanent than a temporary fix is the ticket marked “won’t happen again.”

Interesting facts and historical context (that actually helps you debug)

  • HTTP 500 has been around since the early days of HTTP/1.0, but it was always intended as a catch-all—meaning you must rely on logs, not the status code.
  • WordPress started as a fork of b2/cafelog; its plugin ecosystem exploded early, and that extensibility is a frequent source of runtime surprises.
  • .htaccess exists largely because shared hosting existed: per-directory overrides let users change rewrites without root. It also lets them break things without root.
  • “White Screen of Death” is often a PHP fatal with error display disabled. Users see blank; logs see the truth.
  • PHP-FPM became the common deployment model because it handles process management better than classic CGI and is more flexible than mod_php for modern stacks.
  • OPcache dramatically reduces CPU by caching compiled bytecode, but stale cache edge cases can mimic “random” 500s after deployments.
  • Inode exhaustion is older than cloud computing: ext filesystems can run out of inodes before bytes, and web apps with many small cache files are prime candidates.
  • Many CDNs historically returned generic 5xx pages even when the origin returned a specific code, which trained teams to debug the wrong layer first.
  • WordPress’s rewrite model depends on front controller routing (index.php handling “pretty URLs”), which is why rewrite misconfig shows up as a total site failure.

FAQ

1) Why does WordPress show a 500 instead of a helpful error?

In production, PHP error display is typically off (correctly), so fatals don’t render a nice stack trace. The web server returns 500 because the upstream failed.
Your helpful error is in the logs.

2) Is a 500 error always caused by plugins?

No. Plugins are common, but misconfigurations, missing PHP extensions, permissions, disk full, and PHP-FPM pool saturation are just as common in real environments.
Start with logs, not superstition.

3) What’s the fastest safe way to disable all plugins?

Rename wp-content/plugins to something else. WordPress won’t load normal plugins. If the site recovers, you’ve confirmed the class of problem.
Remember that mu-plugins still load.

4) If I increase memory_limit, will that fix 500 errors?

Sometimes. If the log says memory exhausted, yes—raising it can restore service. But it can also mask a memory leak or an oversized plugin.
Also ensure you change the limit in the right place (PHP-FPM pool/php.ini), not only in WordPress constants.

5) Why do I get 500 only sometimes?

Intermittent 500s usually come from load-dependent behavior: PHP-FPM saturation, a slow database, race conditions in caching layers, disk filling up gradually,
or a flaky upstream dependency. Look for correlations with traffic and resource metrics.

6) What if the logs are empty?

Then you’re looking in the wrong place or logging is misconfigured. Confirm which web server is active, confirm log paths, and check journald.
Also verify that your request actually reaches the origin (bypass CDN/WAF and confirm).

7) I’m on Apache. Should I delete .htaccess?

Don’t delete; quarantine it. Rename it so you can restore quickly. If removing it fixes the issue, rebuild a minimal WordPress .htaccess and reapply changes carefully.

8) Could a database issue show up as 500?

Yes. While WordPress often shows a DB connection message, some object cache layers and custom code throw exceptions that become 500s.
Check PHP logs for database exceptions and web logs for upstream timeouts.

9) How do I know if it’s PHP-FPM vs WordPress code?

Create a temporary minimal PHP endpoint (or use an existing health check) and request it through the same vhost.
If that works, PHP-FPM is probably fine and WordPress code path is failing. Remove the endpoint afterward.

10) What’s the single most effective prevention for recurring 500 incidents?

A deploy pipeline with smoke tests and a one-step rollback. You can survive broken plugins if you can revert in seconds instead of debating in chat.

Conclusion: next steps that actually reduce repeats

WordPress 500 errors aren’t rare, and they aren’t mystical. They’re your system telling you “something failed” while refusing to say what.
Your job is to make it talk: confirm the layer, read the logs, isolate the runtime, then roll forward with a fix—or roll back with confidence.

If you want a practical sequence to implement after the fire is out, do this:

  1. Make sure Nginx/Apache error logs and PHP-FPM logs are retained, rotated, and searchable.
  2. Add a small set of smoke tests that run on every deploy and every config change.
  3. Put guardrails on storage: alerts for disk and inode usage, plus sane cache locations and limits.
  4. Standardize file ownership and deploy mechanics so permission drift stops being a recurring character in your incident reports.
  5. Stage plugin/theme updates, and treat PHP upgrades as compatibility projects, not “minor maintenance.”

Do those, and the next time you see “500 Internal Server Error,” it’ll be a 15-minute fix with a clean postmortem—rather than an evening of interpretive debugging.

← Previous
Power Supplies for Modern GPUs: How to Avoid Pain
Next →
ZFS Write Errors: The Failure Pattern That Predicts a Dropout

Leave a comment