Troubleshooting

How to fix a 500 Internal Server Error: step-by-step troubleshooting for WordPress and servers

A 500 Internal Server Error page on a monitor and the troubleshooting path through the server's error logs

Fixing a 500 Internal Server Error starts with one simple fact: that message is the vaguest one on the web. The server only says "something went wrong" and doesn't spend a single word on where. The good news is that the vagueness exists only on the page: the real reason is always recorded in one of the server's logs, and fixing a 500 starts the moment you open that log. This guide walks that path step by step: where each log lives, which causes are the most common, in order of frequency, what you can do on shared hosting without SSH, and how to disable a broken WordPress plugin without needing wp-admin.

What exactly does a 500 Internal Server Error mean?

HTTP status 500 is a catch-all code for server-side errors: your request arrived intact, the application started building the response and fell over halfway through, whether from a PHP fatal error, a broken directive in .htaccess, or a file it is not allowed to execute. Despite the name, the problem is almost never the server itself (the hardware and the network); it is the code and the configuration running on it.

That definition also draws the line between a 500 and its relatives. In a 502, the request never reaches the application, because the backend is dead or the proxy has the wrong address for it; in a 500, the application is alive and has even received the request. It just could not manage to build the answer:

CodeWhat happenedUsual culpritFirst step
500 Internal Server ErrorThe application crashed while building the responseA plugin or PHP code, .htaccess, permissions, a full diskRead the error log (this article)
502 Bad GatewayThe proxy got no valid response from the backendPHP-FPM is down, a wrong socket, not enough RAMsystemctl status php8.3-fpm
503 Service UnavailableThe service is "temporarily unavailable", on purpose or under loadWordPress maintenance mode, exhausted resourcesDelete the .maintenance file, check the load
504 Gateway TimeoutThe backend is alive but answered too lateA heavy query, a short timeoutOptimize the code, or raise the timeout in a targeted way

If your message is a 502, it has a different troubleshooting path, which we cover separately and in full in our guide to fixing a 502 Bad Gateway error. This article focuses on the 500: "my own code or config is broken."

The golden rule: the cause isn't on the page, it's in the log

The blank 500 page says nothing on purpose: showing error details to visitors would reveal file paths and the structure of your code to everyone, attackers included. The golden rule is this: every 500 fix starts with opening the log, not with guessing and blind restarts. Depending on your stack, the log is here:

# Nginx
sudo tail -f /var/log/nginx/error.log
# Apache on Debian/Ubuntu
sudo tail -f /var/log/apache2/error.log
# Apache on AlmaLinux/Rocky
sudo tail -f /var/log/httpd/error_log
# cPanel (Apache or LiteSpeed)
sudo tail -f /usr/local/apache/logs/error_log
# DirectAdmin (one file per domain)
sudo tail -f /var/log/httpd/domains/example.com.error.log
# PHP-FPM pool log on AlmaLinux/Rocky
sudo tail -f /var/log/php-fpm/www-error.log
# WordPress debug log (only exists after enabling WP_DEBUG_LOG)
tail -f /var/www/example.com/wp-content/debug.log

A note for Ubuntu: if no separate log has been set up for the pool, PHP fatal errors show up inside Nginx's own error.log, prefixed with FastCGI sent in stderr:

2026/08/06 10:41:23 [error] 1812#1812: *44 FastCGI sent in stderr:
"PHP message: PHP Fatal error:  Uncaught Error: Call to undefined function
old_slider_init() in /var/www/example.com/wp-content/plugins/old-slider/old-slider.php:87"
while reading response header from upstream, client: 203.0.113.7

The right way to work is to reproduce the error with the log in view: keep tail -f running in one terminal and request the broken page from a second terminal (or the browser). The line that appears at that exact moment is your case file. Don't trust the browser for the real status code either: the browser and the CDN sometimes show a healthy cached copy while everyone else gets a 500:

curl -s -o /dev/null -w "%{http_code}\n" https://example.com/
# 500
curl -sI https://example.com/ | head -n 5
# check x-cache / cf-cache-status headers to spot cached replies

If you're not comfortable with tail, grep and reading logs, a quick look through our essential Linux commands puts exactly these tools in your hands.

💡 Tip: the last line of the log is not necessarily from your error; it may belong to a bot or to another visitor. Always match the timestamp of the log line against the moment you reproduced the error, which is why a live tail -f beats opening the file after the fact.
Flowchart for troubleshooting a 500 error: read the server log, then follow one of three branches (a PHP fatal error, file permissions, or running out of memory or disk) to the fix for that branch

Common causes of a 500 error, in order of frequency

Start with the first one, and don't move down the list unless the log tells you otherwise.

1. A PHP fatal error after a plugin or theme update

The most frequent scenario in WordPress: a plugin was updated last night, and today the whole site, even wp-admin, is blank. The log usually names the culprit outright, because the path of the failing file sits inside that plugin's folder. The fix doesn't need wp-admin; over SSH, rename the plugin's folder so WordPress no longer loads it:

cd /var/www/example.com/wp-content/plugins
mv old-slider old-slider.off
# not sure which plugin? disable them all at once:
mv /var/www/example.com/wp-content/plugins /var/www/example.com/wp-content/plugins.off

If the theme is to blame, do the same with the theme's folder in wp-content/themes, so that WordPress falls back to a default theme (if one of the Twenty themes is installed). Once the site is back up, restore the folder name and activate the plugins one at a time to find the offender. If you run WordPress on your own server, our guide to installing and moving WordPress to a VPS shows the correct layout of these paths.

There is also a shortcut: since version 5.2, WordPress has a Recovery Mode. If the fatal error is of a kind it can catch, instead of a blank page it shows the message "There has been a critical error on this website" and emails the site admin a link that lets you into the dashboard while the broken plugin or theme is paused for your session only. It comes with three real caveats. First, the email is sent from the server itself, and if the server's mail is treated as spam it never arrives; WordPress also sends it at most once a day (the time of the last send is stored in the recovery_mode_email_last_sent option), so if the first one lands in spam, there is no second email for 24 hours. You can send it to a more reliable address than the admin email with the RECOVERY_MODE_EMAIL constant in wp-config.php. Second, the email is only sent when the error happens in wp-admin or wp-login.php, not during wp-cron and background jobs, and not when only public pages are loading; an error that occurs only on the front end shows the critical-error page but sends no email, so open /wp-admin/ once to get the recovery link. Third, it only catches errors that happen after the handler itself has loaded: a syntax error in wp-config.php, a broken .htaccess, and memory exhaustion that kills the process before WordPress's shutdown code runs all slip past it. These are the classic blank 500s, and the way to them is the same log. (A parse error inside a plugin, however, is caught: WordPress's handler covers E_PARSE alongside E_ERROR.)

2. A broken or overly strict .htaccess file

On Apache and LiteSpeed, a single typo in .htaccess, or a rule added by a security plugin, is enough for a permanent 500; Apache's log then says something like Invalid command 'RewriteRul', perhaps misspelled. There is a one-line test:

cd /var/www/example.com
mv .htaccess .htaccess.broken
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/

If the code goes from 500 back to 200 (or 301), you have found the culprit. For WordPress, the simplest fix is to rebuild the standard file, either from the dashboard (Settings → Permalinks → Save) or by hand:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Nginx doesn't read .htaccess at all; if your stack is Nginx, skip this section. Our guide to setting up a site with Nginx shows where these rules belong instead.

3. Running out of memory_limit

Its signature in the log is unmistakable:

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted
(tried to allocate 2621440 bytes) in /var/www/example.com/wp-includes/class-wp-image-editor-gd.php

The number 134217728 is 128 megabytes: the memory limit per request, not for the whole server. Usually a single heavy operation (editing a large image, an import, a WooCommerce report) blows through it. Raise the limit deliberately, one step at a time:

# replace 8.3 with your PHP version (php -v)
grep "^memory_limit" /etc/php/8.3/fpm/php.ini
# memory_limit = 128M
sudo sed -i 's/^memory_limit.*/memory_limit = 256M/' /etc/php/8.3/fpm/php.ini
sudo systemctl reload php8.3-fpm

WordPress adds a second layer that confuses a lot of people: at the start of every request, WordPress itself raises the limit to WP_MEMORY_LIMIT (40 MB by default, 64 on multisite), and in the dashboard to WP_MAX_MEMORY_LIMIT (256 MB by default). It never lowers it, and it can only raise it if the host allows memory_limit to be changed at runtime. So if you have no access to php.ini on shared hosting, try this line in wp-config.php; if the number in the log's error line doesn't change, the limit has to be raised from the hosting panel's php.ini editor:

define( 'WP_MEMORY_LIMIT', '256M' );

"Deliberately" means not blindly setting 2 GB. If a page dies even at 512 MB, you are dealing with a memory leak or a faulty loop, and a higher limit only postpones the crash. Besides, this number multiplied by the number of workers has to fit in the server's RAM: with pm.max_children = 20 and a 256 MB limit, the worst case is 5 GB, so on a server with 4 GB of RAM this very "fix" ends with the OOM Killer a few hours later.

4. PHP version incompatibility after an upgrade

The server has been upgraded to PHP 8.3, and an old plugin still calls create_function() or each(), functions that were removed in PHP 8. The result is a fatal error and a 500 on every page that touches that code. This scenario comes up a lot after moving to a newer distribution: Ubuntu 24.04 installs PHP 8.3 by default, and AlmaLinux 9 ships with PHP 8.0 and gets newer versions from a module stream or the Remi repository. Code that ran fine on the old server with PHP 7.4 dies here on the very first request. Once again, the log gives you the file path; the lasting fix is to update or replace that plugin, and the temporary fix is to switch that site (not the whole server) back to the previous PHP version, so you have time to fix it.

5. Broken permissions and ownership

One hasty chown or chmod -R, or uploading files as root, is enough for PHP to lose the ability to read or write the files. On some configurations (suPHP and suexec on cPanel), even 777 permissions deliberately get a 500, because they are treated as insecure. The safe, standard values are 644 for files and 755 for folders, owned by the same user the pool runs as:

grep "^user" /etc/php/8.3/fpm/pool.d/www.conf
# user = www-data
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;

6. A full disk

When there is no room to write, PHP sessions can't be created, cache and temporary files fail, and an application that relies on those writes goes down with a 500. Worse still, the log itself stops being written:

df -h
df -i
# find what is eating the disk
sudo du -xh /var --max-depth=2 | sort -rh | head -n 10

Take df -i seriously: a disk that "has space" but has run out of inodes (for example, because of millions of tiny session files) produces exactly the same symptoms. To find out before it fills up next time, set up automatic disk usage alerts with our Linux server monitoring guide.

7. Stale OPcache after a deploy

OPcache keeps compiled PHP bytecode in memory so that each request doesn't have to compile it again. If, right after a deploy or an update, you see strange errors such as Cannot redeclare class, or calls to a method that "definitely exists", you probably have a mix of old bytecode and new files running, especially when opcache.validate_timestamps=0 is set. The fix is one line, and the safest form of it is reloading the service:

sudo systemctl reload php8.3-fpm

If you use that setting for speed, make the reload a fixed step in your deploy script. We cover OPcache's role in performance in full in our guide to speeding up WordPress.

When the web server itself returns a 500: an internal redirect loop

Not every 500 comes from PHP. When a rewrite rule keeps sending a request back to itself, Apache and Nginx both give up after a few rounds and return a 500 themselves, without PHP ever running. The signature of this case in the log is unmistakable, and if you see it, reading the PHP log is a waste of time:

# Apache: a RewriteRule that keeps matching its own target
[core:error] [pid 1912] [client 203.0.113.7:51234] AH00124: Request exceeded the limit
of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion'
to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.

# Nginx: try_files that falls back to a URI handled by the same location
2026/09/14 22:10:05 [error] 2231#2231: *17 rewrite or internal redirection cycle
while internally redirecting to "/index.php", client: 203.0.113.7, server: example.com,
request: "GET / HTTP/2.0", host: "example.com"

On Apache, the culprit is usually a RewriteRule without the !-f and !-d conditions, or a custom error page that itself falls under the same rule. Don't raise the limit of 10 rounds with LimitInternalRecursion; break the loop. On Nginx, the most common form is that the try_files target (for example /index.php) doesn't exist under the root or alias path, because of a wrong root or a file that was moved, so the internal redirect lands in the same location / again and reaches the same missing target once more. (If only the location ~ \.php$ block is missing, the result is not a 500: Nginx serves index.php as a static file and leaks your PHP source.) Testing the configuration before every reload is mandatory:

sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo apachectl configtest
# Syntax OK
⚠️ nginx -t and configtest only check the syntax, not the logic. A redirect loop passes both with "Syntax OK" and only shows itself on the first real request, so after every change to a rewrite rule, run one test with curl from the server itself.

Why does only wp-admin return a 500 while the site works?

This pattern is so common that it is a search of its own: the home page loads, but logging in to the dashboard returns a 500. There are only three reasons, and the log decides between them.

First, memory. The dashboard loads far more code (plugin dashboards, WooCommerce reports, thumbnail regeneration with GD), and, as you saw above, WordPress tries to raise the limit to 256 MB for the dashboard. If the host doesn't allow memory_limit to be changed at runtime, the dashboard is stuck with the same 128 MB as the front end (or less), and the first heavy task blows through it. An Allowed memory size line in the log tells you exactly that, and the fix is raising the limit in php.ini, not in wp-config.

Second, code that only runs in the dashboard. Many plugins load their admin section on the admin_init or admin_menu hook; if that section is incompatible with the new version of PHP or WordPress, the site stays healthy and the dashboard dies. The file path in the log points straight at the plugin's folder, and the same folder rename finishes the job.

Third, a second .htaccess inside wp-admin. To protect the dashboard, security plugins and some guides put an .htaccess file with a separate password (Basic Auth) inside this folder. If the password file is moved, or the site is migrated to another server and the AuthUserFile path no longer exists, Apache returns a 500 instead of the password prompt and logs AH01620: Could not open password file. The test is the same familiar one-liner:

ls -la /var/www/example.com/wp-admin/.htaccess
mv /var/www/example.com/wp-admin/.htaccess /var/www/example.com/wp-admin/.htaccess.off
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/
# 302 (redirect to wp-login.php) means the admin is alive again

Turn on WP_DEBUG safely

When the server log is not available for some reason (on shared hosting, for example), WordPress can write its own log. In wp-config.php, add these three lines above the line /* That's all, stop editing! */:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

From then on, all errors and warnings are collected in wp-content/debug.log. Reproduce the error once, read the file, and when you have finished troubleshooting, set all three back to how they were: the debug log grows fast on a busy site. If you want to keep the log file out of public reach, you can give WP_DEBUG_LOG a full path outside the site's public folder instead of true; the official WordPress documentation also uses a path outside the web root (such as /tmp/wp-errors.log) as its example. Our reason for recommending it is security: a debug.log inside wp-content can be read in a browser at a simple address.

⚠️ Never set WP_DEBUG_DISPLAY to true on a live site: the error details, including full file paths and sometimes fragments of queries, end up in front of every visitor and crawler. For the same reason, display_errors = Off in a live site's PHP is a correct setting, not a problem to fix; errors belong in the log (log_errors = On), not on the page.

Fixing a 500 error on shared hosting without SSH

On shared hosting you have neither tail -f nor the server's php.ini, but the same logic works with the hosting control panel's tools (cPanel or DirectAdmin). On Mehran Host shared hosting, you do these tasks inside the Mehran Host panel itself, not in cPanel or DirectAdmin: renaming a plugin's folder, temporarily moving .htaccess aside and restoring 644/755 permissions can all be done from the Files tab (the panel's file manager has rename and chmod), while for reading the error log and changing the PHP version or memory_limit, you ask support in a ticket. If you're with another host and have access to cPanel or DirectAdmin itself, the path looks like this:

  1. Read the log from the panel. In cPanel, the "Errors" page (under the Metrics section) shows up to the last 300 lines of the web server's error log, newest first; for more than that you have to download the log file. In DirectAdmin, open the Error Log for each domain from "Site Summary / Statistics / Logs"; it is the same /var/log/httpd/domains/example.com.error.log file the server administrator sees. Here too, a Fatal error line with a file path closes the case.
  2. Disable the plugin from the file manager. In File Manager, go to wp-content/plugins and rename the guilty plugin's folder, exactly what you would do with mv over SSH. The same tool is also enough to move .htaccess aside temporarily.
  3. Set the PHP version and memory_limit from the panel. Both panels have a page for choosing each domain's PHP version and editing php.ini values. Temporarily lowering the version (for example, from 8.3 to 8.1) after a 500 caused by an old plugin, and raising memory_limit after an Allowed memory size line, are both done here.
  4. Undo 777 permissions. On cPanel with suPHP or suexec, a file or folder set to 777 deliberately gets a 500; in the file manager, set the value back to 644 for files and 755 for folders.
💡 Tip: on shared hosting, a full disk is usually a full quota, not a full physical disk. Once the account's space quota runs out, PHP can't write sessions and temporary files, and the symptoms are exactly those of a full disk on a server. Check the disk usage bar on the panel's front page before you do anything else.

When the error comes and goes

The log solves a permanent 500 in a few minutes; an "every now and then" 500 is sneakier, because whenever you look, everything is fine. It has two classic patterns.

Pattern one: the OOM Killer. When RAM runs out, the kernel kills one of the memory-hungry processes to stay alive, often one of the PHP-FPM children or MySQL. Requests that were mid-execution at that moment die with a 5xx error, systemd brings the service back up, and the site "fixes itself", until the next shortage. Get the evidence from the kernel:

sudo dmesg -T | grep -i oom
sudo journalctl -k | grep -i "out of memory"
free -h

Pattern two: cron-hour peaks. If the errors follow a time pattern, such as exactly on the hour or every night around 3 a.m., match the timestamps of the errors in the log against your cron job schedules. A compressed backup, indexing, and wp-cron on a busy site can monopolize the CPU and RAM for several minutes:

crontab -l
ls /etc/cron.d/
# Debian/Ubuntu (service is named "cron")
sudo journalctl -u cron --since "3 hours ago" --no-pager
# AlmaLinux/Rocky (service is named "crond")
sudo journalctl -u crond --since "3 hours ago" --no-pager
sudo tail -n 20 /var/log/cron

The fix is to spread the heavy jobs across different hours and to run backups with nice and ionice (we cover how to schedule and log these jobs properly in our Linux cron job guide), and, if RAM really is short, to upgrade the resources; on a cloud server, that upgrade is just a few clicks.

What does a 500 error do to SEO, and what should you do after fixing it?

Google's official documentation on 5xx errors is explicit: when its crawler sees these errors, it temporarily slows down crawling the site, it ignores any content returned with a 5xx code, and URLs that are already indexed stay in the index at first but, if the error continues, are eventually dropped. How much crawling slows down is also proportional to the number of URLs returning the error, so a 500 on one specific page does not weigh the same as a 500 across the whole site. Recovery is gradual too: once the server returns 2xx again, Google raises the crawl rate step by step, not overnight.

Two practical consequences follow. First, if you are going to take the site down on purpose for maintenance, 500 is the wrong code: a 503 with a Retry-After header tells the crawler "come back later", and that message is safer for your index. Second, after fixing the error, check the "Pages" report in Search Console for the "Server error (5xx)" row, and the "Crawl stats" report to see the crawl rate recover; it takes a few days for the chart to return to normal, and that delay is to be expected. And if there is a CDN or a caching plugin in front of the site, purge the cache once after the fix, and get a 200 with the same curl command from several different pages, not just the home page.

A 10-minute checklist for fixing a 500 error

When the site is down, don't rely on memory; go through these ten steps in order:

  1. Get the real code: curl -s -o /dev/null -w "%{http_code}\n" https://example.com/. It may actually be a 502 or a 503, which changes your path.
  2. Watch the log live: run tail -f on the web server's error log and load the page once while it runs.
  3. Read the error line: the phrase Fatal error plus a file path usually closes the case right there; AH00124 or redirection cycle means the culprit is a rewrite rule, not PHP.
  4. Ask "what changed?": a plugin update, a deploy, a PHP upgrade. The latest change is the first suspect.
  5. Disable the suspect plugin over SSH: renaming its folder in wp-content/plugins is enough.
  6. On Apache/LiteSpeed, move .htaccess aside temporarily (or wp-admin/.htaccess, if only the dashboard is broken) and test again with curl.
  7. Check resources: df -h and df -i for the disk, free -h for RAM.
  8. Look for OOM traces: sudo dmesg -T | grep -i oom, especially if the error is intermittent.
  9. Restore ownership and permissions: 644 for files, 755 for folders, owned by the pool's user.
  10. Still stuck? Run systemctl reload php8.3-fpm for a fresh OPcache, then turn on WP_DEBUG_LOG and reproduce the error once.
💡 Tip: change only one thing at each step, and test right away with the same curl command. If you change three things at once and the site comes back, you won't know which one was the fix, and next time you'll have to start from scratch.

Next step

From now on, a blank 500 page is nothing to be scared of: you open the log, find the Fatal error line, and get to the guilty file in a few minutes. If you want the skill to stick, practice on a server where breaking things doesn't matter: create an hourly cloud server (about 60 seconds later, a KVM server with NVMe storage is ready), deliberately install an incompatible plugin, mangle .htaccess, fill the disk, and each time trace the problem back to its root from the log. The only cost is those few hours of practice.

Frequently asked questions

What does a 500 error mean, and who is to blame?

A 500 error means the request reached the server, but the application crashed while building the response. The culprit is almost always on the site's side: broken code or a broken plugin, a faulty .htaccess file, exhausted memory or disk, or wrong permissions, not the visitor's internet connection or the server hardware. The exact reason is deliberately not shown on the page, but it is always recorded in the web server or PHP error log, and troubleshooting starts by reading that log line.

What is the difference between a 500 and a 502 error?

In a 500, the application is alive and falls over while running its code; in a 502, the proxy (such as Nginx) can't get a valid response from the backend at all, because the backend service is dead or its address is wrong. That is why you fix a 500 by reading the log and correcting the code and configuration, and a 502 by bringing the backend back to life.

How do I fix a WordPress 500 error after installing a plugin, without wp-admin?

Log in to the server over SSH and rename that plugin's folder in wp-content/plugins (to plugin.off, for example); WordPress doesn't load a plugin whose folder is missing, and the site comes back immediately. If you don't know which plugin is to blame, temporarily rename the whole plugins folder, then reactivate the plugins one by one.

Why does a 500 error sometimes come and go on its own?

An intermittent 500 error usually means a resource shortage, not a fixed bug: either the OOM Killer kills a process at moments of heavy load, or a heavy cron job monopolizes resources at a particular hour, or only certain heavy pages hit the memory_limit ceiling. The exact times of the errors in the log give the pattern away.

Does a 500 error affect SEO and rankings?

Yes, but the effect depends on how long the error lasts. According to Google's documentation, 5xx errors make the crawler temporarily slow down crawling the site and ignore the content of the failing pages; indexed pages stay in the index at first, but are eventually dropped if the error continues. An outage of a few minutes has practically no effect; one lasting several days does. After the fix, the crawl rate recovers gradually, and the "Server error (5xx)" row in Search Console should clear within a few days.

Related tutorials

Ready for some hands-on practice?

A Mehran Host hourly cloud server is ready in under 60 seconds. Practice as much as you like and pay only for the hours you use. Estimate the cost with the pricing calculator before you sign up.