# HTTP 500 Internal Server Error: Meaning and Fixes

> 500 means the server hit an error it did not handle. Find which layer sent it, read the right log for nginx, Apache, WordPress, Django, Next.js or Express.

Source: https://howhttpworks.com/status-codes/500
Last reviewed: 2026-10-05

> **TL;DR:** HTTP 500 Internal Server Error means the server failed while handling your request. Find the layer that returned it, then match the request ID and timestamp to its error log; the exception there tells you what to fix.

## What it means

RFC 9110 section 15.6.1: the server encountered an unexpected condition that prevented it from fulfilling the request. A bad payload can expose a server bug too: 500 describes the failure, not whether the input was valid. Start with the server exception rather than guessing from the status.

Server responses and client messages below are illustrative.

```http
HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{"error":"internal_error","requestId":"req-8f3a1c"}
```

## What you see in your client

These messages assume the response reason is `Internal Server Error`; a server can use different wording.

- **Axios:** `AxiosError: Request failed with status code 500`. With the default `validateStatus`, Axios rejects this response. Inspect `error.response.status`, `error.response.headers` and `error.response.data` before changing the request. [Axios source](https://github.com/axios/axios/blob/v1.x/lib/core/settle.js).
- **fetch:** the promise resolves with `response.status === 500` and `response.ok === false`. Check `ok` yourself; a `catch` block alone misses HTTP error responses. [Fetch behaviour](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).
- **Python requests:** `requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://example.com/api`. This appears when you call `response.raise_for_status()`. Save the response body and headers before raising. [Requests source](https://github.com/psf/requests/blob/main/src/requests/models.py).
- **curl -f:** `curl: (22) The requested URL returned error: 500`. Use `curl -sS -D - https://example.com/api` while diagnosing so you retain the error body. [curl source](https://github.com/curl/curl/blob/master/lib/http.c).
- **.NET:** `System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).` This is the English message from `EnsureSuccessStatusCode()` with that reason phrase. Inspect `StatusCode` and read the body before calling it. [Runtime source](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs), [message resource](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/Resources/Strings.resx).
- **Spring WebClient:** `org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from GET https://example.com/api`. `retrieve()` uses this subclass by default; read `getResponseBodyAsString()`. RestTemplate uses `HttpServerErrorException.InternalServerError` for this 5xx status. [WebClient source](https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java), [RestTemplate handler](https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java).

## Who sent it?

| Signal | Layer |
| --- | --- |
| Body is your app's JSON or framework error page | Application: stack trace in app logs |
| `Server: Apache`, "The server encountered an internal error or misconfiguration" | Apache: `.htaccess` or CGI/handler failure |
| `Server: nginx` with the default page "500 Internal Server Error" | nginx itself: check `error.log` for `rewrite or internal redirection cycle` |
| `Server: cloudflare` and "Error 1101: Worker threw exception" | A Cloudflare Worker threw; see Workers logs |
| `Server: cloudflare` with your origin's 500 body | Origin returned 500; Cloudflare passed it through |
| Cloudflare page "Error 520" | Not a 500; origin response was empty or invalid, see [520](https://howhttpworks.com/status-codes/520) |

If a CDN is involved, bypass it and hit the origin directly to see whether the 500 is yours:

```bash
curl -sv https://example.com/api/orders -o /dev/null 2>&1 | grep -iE '^< (HTTP|server|cf-ray|via|x-request-id|x-amz)'
curl -sv --resolve example.com:443:ORIGIN_IP https://example.com/api/orders -o /dev/null
```

Every response should carry a request ID header (`X-Request-ID`) that is also logged by the app, so you can find the exact log line.

## Fix it by stack

Match the exception to the deployed application version and configuration before changing a setting.

## Common causes by stack

### Apache and .htaccess

```text
[core:alert] [pid 1234] /var/www/html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
[cgi:error] Premature end of script headers: index.php
```

The first points to an unavailable directive: check that `mod_rewrite` is loaded (`a2enmod rewrite` on Debian/Ubuntu). With both `AllowOverride None` and `AllowOverrideList None`, Apache ignores `.htaccess` entirely; it does not report this syntax error. The second points to a CGI handler that ended without headers: read its stderr/PHP log. Run `apachectl configtest` for the main configuration, then reproduce the request to exercise `.htaccess`. Check ownership and access for the actual Apache user. [Apache configuration](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride).

### WordPress and PHP

PHP fatal errors are hidden when `display_errors` is off, so enable logging instead of display:

```php
// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);       // writes wp-content/debug.log
define('WP_DEBUG_DISPLAY', false);
define('WP_MEMORY_LIMIT', '256M');
```

Read `wp-content/debug.log` for the failing plugin, theme or PHP fatal. If it says `Allowed memory size of 134217728 bytes exhausted`, that number is the limit in that message, not a universal WordPress default. The `256M` above is a chosen troubleshooting value. Disable the identified plugin; check `.htaccess` when Apache logs a directive error. [WordPress debugging](https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/).

### Node, Express and Next.js

Express uses `err.status` or `err.statusCode` when it is a valid 4xx/5xx value, otherwise 500. Express 5 forwards rejected route promises to `next`; in Express 4, use `try/catch` and `next(err)`. Put the four-argument error handler after routes, and delegate if headers were already sent. [Express error handling](https://expressjs.com/en/guide/error-handling/).

```javascript
// Global error handler: must have 4 parameters and come last
app.use((err, req, res, next) => {
  if (res.headersSent) return next(err)
  req.log?.error({ err, path: req.path })       // full detail server-side only
  const candidate = err.status ?? err.statusCode
  const status = Number.isInteger(candidate) && candidate >= 400 && candidate < 600 ? candidate : 500
  res.status(status).json({ error: status < 500 ? err.message : 'internal_error', requestId: req.id })
})
```

Next.js hides Server Component error details in production. Use `error.digest` from the error boundary to match the server log entry; the digest is generated for that error. [Next.js error boundary](https://nextjs.org/docs/app/api-reference/file-conventions/error).

### Django, Rails, Spring

- **Django/DRF:** an exception outside DRF's handled exceptions is re-raised and becomes Django's 500 response. With `DEBUG = False`, read the server traceback rather than the generic page; inspect database/migration errors there. [DRF exception handling](https://www.django-rest-framework.org/api-guide/exceptions/).
- **Rails:** exceptions absent from `config.action_dispatch.rescue_responses` map to 500. Match the request ID in your production log and fix the exception, rather than mapping every exception to 200. [Rails exception mapping](https://guides.rubyonrails.org/configuring.html#config-action-dispatch-rescue-responses).
- **Spring Boot:** the browser's Whitelabel Error Page comes from the default error handling. Read the application stack trace for the failing controller or dependency. [Spring Boot error handling](https://docs.spring.io/spring-boot/how-to/spring-mvc.html).
- **FastAPI:** returning data that fails `response_model` validation produces a server error. Fix the returned fields/types; this is different from invalid request input. [Response validation](https://fastapi.tiangolo.com/tutorial/response-model/).
- **ASP.NET Core:** an unhandled exception before response headers produces 500; after headers, the server closes the connection. Use `UseExceptionHandler` in production and find the exception in application logs. [ASP.NET Core error handling](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling).
- **Laravel:** an exception that is not an HTTP exception becomes a 500 in the default handler. Read the exception in the configured log channel; keep `APP_DEBUG=false` in production. [Laravel handler](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Foundation/Exceptions/Handler.php), [production configuration](https://laravel.com/docs/12.x/deployment#debug-mode).
- **API Gateway/Lambda:** a rejected Lambda invocation produces 500; a function error or invalid response produces 502 instead. Inspect invocation permissions and the gateway/Lambda logs. [AWS error mapping](https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway-errors.html).
- **nginx/Cloudflare:** `rewrite or internal redirection cycle` is nginx's 500 after its internal-redirect limit; fix the `rewrite`/`error_page` loop. Workers error 1101 means a JavaScript exception; inspect Workers logs. [nginx internal redirects](https://nginx.org/en/docs/http/ngx_http_core_module.html#internal), [Workers errors](https://developers.cloudflare.com/workers/observability/errors/).

### Resource exhaustion, whatever the stack

A handled resource failure can become 500; a killed process can instead leave a proxy returning 502. Check the exception for `No space left on device`, a database pool timeout or `EMFILE`, and check Kubernetes pod state for `OOMKilled`. Check `df -h`, `free -m`, `dmesg | grep -i oom`, and the database connection count.

## A 500 on POST /login

Reproduce the failing method and path, then search the server log by request ID. Trace user lookup, password verification and session creation in that order; stop at the first exception. Redact passwords, cookies and tokens from the log. If the traceback points to a database/schema error, verify the deployment's database connection and migration state. An Axios 500 here sends you to those server logs, not to Axios configuration.

## Build it better

Return a short, stable body with a request ID, and never leak internals. RFC 9457 `application/problem+json` is the standard shape:

```http
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{"type":"about:blank","title":"Internal Server Error","status":500,"instance":"/req/8f3a1c"}
```

Alert on the 5xx rate, not on individual errors, and keep stack traces in your logging system (Sentry, OpenTelemetry), not in responses.

## Related codes

- [502 Bad Gateway](https://howhttpworks.com/status-codes/502): a proxy got a bad response from the app behind it.
- [503 Service Unavailable](https://howhttpworks.com/status-codes/503): the app cannot take the request right now.
- [504 Gateway Timeout](https://howhttpworks.com/status-codes/504): a gateway timed out connecting or waiting for an upstream.
- [520 Web Server Returned an Unknown Error](https://howhttpworks.com/status-codes/520): Cloudflare received an empty or invalid origin response, often a crashed app.
- [debug: 500 Internal Server Error](https://howhttpworks.com/debug/500-internal-server-error): step-by-step triage.
