HTTP 500 Internal Server Error - Causes, Diagnosis and Fixes
HTTP 500 means the server hit an unexpected condition and could not complete the request. It is a generic catch-all, usually raised by your application code or runtime rather than by a proxy. The real cause is almost always written in the application or web server error log at the moment of the failing request.
An HTTP 500 Internal Server Error means the server encountered an unexpected condition that stopped it from fulfilling the request. RFC 9110 (section 15.6.1) defines it exactly that broadly: it is the generic "something went wrong on our side" response, used when no more specific 5xx code fits. In practice it almost always comes from your application or its runtime, and the cause is recorded in a log file you can read.
What the 500 status code means
The 5xx class tells the client that the server is aware it has failed or cannot perform the request. Within that class, 500 is the least specific. Compare it with its neighbors:
| Code | Who usually sends it | What it says |
|---|---|---|
| 500 | The application or web server itself | Unexpected failure while handling the request |
| 502 | A proxy, load balancer or CDN | The upstream server sent an invalid response |
| 503 | App, proxy or load balancer | Temporarily overloaded or in maintenance |
| 504 | A proxy, load balancer or CDN | The upstream server did not answer in time |
So if you see a 500, start your investigation at the application layer, not at the proxy.
Who returns a 500
- Application frameworks. Django, Rails, Laravel, Express, ASP.NET, Spring and others convert an unhandled exception into a 500 response. In production mode they hide the stack trace and log it instead.
- Language runtimes. PHP returns a 500 on a fatal error when
display_errorsis off. A crashing Node.js handler that never responds is more likely to cause a 502 or 504 at the proxy, but an error middleware that catches it will send a 500. - Web servers. Apache returns 500 for a broken
.htaccessfile (for example an unknown directive or a module that is not loaded). nginx can return 500 for internal problems such as a rewrite loop ("rewrite or internal redirection cycle"). - Platforms and CDNs. Some hosting platforms and CDNs pass through the origin's 500 unchanged, so the code you see in the browser is still your app's.
Common causes
- Unhandled exceptions in code. A null reference, a missing key, a type error, a failed assertion. This is the most common cause by far in day-to-day operations.
- A bad deploy. New code, a missing environment variable, an unmigrated database schema or a dependency that did not install.
- Database errors. Connection refused, too many connections, a deadlock, a query timeout or a missing table that the code does not handle gracefully.
- Configuration errors. Invalid
.htaccessdirectives, wrong file permissions on config files, a malformed JSON or YAML config the app parses at request time. - Resource exhaustion. Disk full (sessions, logs or uploads cannot be written), memory limits such as PHP's
memory_limit, or file descriptor limits. - Failing third-party calls. An API your code calls returns an error and the exception is not caught.
How to diagnose a 500
1. Reproduce it and look at the response
curl -sS -o /dev/null -w "%{http_code} %{time_total}s\n" https://example.com/checkout
curl -I https://example.com/checkout
curl -I sends a HEAD request and shows the headers. Some apps handle HEAD differently from GET, so if HEAD looks fine, repeat with a GET (curl -sS -D - -o /dev/null URL). Check the Server header and any request ID header your stack adds; that ID is what you search for in the logs.
Note whether it fails on every URL or only some. A site-wide 500 points to configuration, a deploy or the database. A 500 on one route points to the code behind that route.
2. Read the logs at the exact time of the request
Typical locations:
# nginx and Apache
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/apache2/error.log # Debian/Ubuntu
sudo tail -n 100 /var/log/httpd/error_log # RHEL family
# PHP-FPM (path varies by version)
sudo tail -n 100 /var/log/php8.2-fpm.log
# App running under systemd
sudo journalctl -u myapp --since "10 minutes ago"
# Docker
docker logs --since 10m my-app-container
Framework logs live in the app itself, for example storage/logs/laravel.log in Laravel or log/production.log in Rails. Look for the first stack trace near the timestamp of your failing request. Later errors are often side effects of the first one.
3. Check what changed
git log --since="2 hours ago" --oneline
df -h # is the disk full?
free -m # is memory exhausted?
If the errors started at the time of a deploy, rolling back is usually faster than debugging live.
4. Turn on detail safely
Do not enable debug output on a public production site, because stack traces can expose secrets. Instead, raise log verbosity temporarily, or reproduce the request against a staging copy with debug mode on.
How to fix it
- Fix the exception, then handle the edge case. Add the missing null check, validate input and return a 4xx for bad input instead of letting it bubble up as a 500.
- Roll back a bad release and redeploy once the cause is found. Make migrations part of the deploy so code and schema stay in step.
- Repair configuration. Run
apachectl configtestornginx -tafter any config change, and check that config files are readable by the service user. - Free resources. Clear or rotate logs, raise memory limits where justified, and add connection pooling if the database is running out of connections.
- Catch upstream failures. Wrap calls to third-party APIs with timeouts and error handling so one slow vendor does not turn into a 500 for your users.
- Add error tracking. An exception tracker or structured logs with request IDs turn "something broke" into a specific line of code.
How to get alerted about 500 errors
Your users should not be the first to tell you. An external HTTP monitor requests your URL on a schedule and alerts you when the status code is outside the 2xx/3xx range. A few practical points:
- Monitor more than the home page. A cached home page can return 200 while the login or checkout route throws 500. Add a monitor for each critical path.
- Use a keyword check when an error page might be served with status 200. Some misconfigured apps render a friendly error template without setting the status code; checking for a word that only appears on a healthy page catches that.
- Re-check before alerting, so a single transient exception does not wake anyone up.
PingRobot does this with HTTP(S) and keyword monitors that run every 5 minutes on the Free plan or every minute on Pro; a failure is re-checked from a second region before an alert goes out by email. If you are seeing other 5xx codes as well, see the guides to 502 Bad Gateway, 503 Service Unavailable and 504 Gateway Timeout, or work through the website down checklist.
Start monitoring free — 10 monitors, no credit card.
Frequently asked questions
Is a 500 error my fault or the visitor's fault?
It is a server-side error. The request may have contained something unusual, but a correctly built server should answer a bad request with a 4xx code, so a 500 always points to something the site owner needs to fix.
What is the difference between 500 and 502?
A 500 is produced by the server that tried to handle the request itself. A 502 is produced by a proxy or gateway that received an invalid response, or no usable response, from the server behind it.
Should a monitor treat a single 500 as downtime?
A single 500 can be a one-off exception, so it is sensible to re-check before alerting. If the re-check also fails, treat it as an outage, because real users are getting the same error.
Does a 500 error hurt SEO?
Short, occasional 500s are generally treated by search engines as temporary. Errors that persist for a long time can lead crawlers to slow down or drop pages, so fix recurring 500s quickly.