A server error 500 means the server encountered an unexpected condition that prevented it from completing the request. It is a generic server-side failure, not a diagnosis: the useful cause is in the correlated edge, application, runtime, system, or dependency evidence.
RFC 9110 section 15.6.1 defines the status precisely. MDN's practical 500 reference describes it as the catch-all response used when a more specific 5xx status does not fit.
What to do when a website shows error 500
The failure must be investigated by the service operator. A visitor cannot repair the server, but can avoid making the situation worse:
- Record the time, the page or action, and any request or support ID displayed by the site.
- Retry an ordinary read-only page once after a short pause.
- Check the service's official status page when one exists.
- Do not repeatedly submit a checkout, transfer, upload, account change, or other action with side effects. The server might have completed the operation before failing to build the response.
- Before retrying a state-changing action, check whether the result already exists.
- When contacting support, send the request ID, time with time zone, and URL path. Remove query values, tokens, passwords, and personal data.
Refreshing, changing browsers, or clearing local data cannot fix a server defect. Those steps are useful only if the operator has evidence that the client state triggered a known server-side bug.
Start with evidence, not a restart
For operators, the first objective is not “make 500 disappear.” It is to preserve enough evidence to identify the component and failure mode.
Capture:
- UTC timestamp and time window;
- request ID or correlation ID;
- host, HTTP method, normalized route, and returned status;
- release or deployment identifier;
- response headers that identify the serving path, after removing sensitive values;
- matching edge, access, application, runtime, system, and dependency events;
- whether the request may already have committed a state change.
Do not begin by restarting workers, rotating credentials, changing permissions, scaling capacity, or editing configuration. Those are state-changing mitigations that can destroy evidence or introduce a second variable. Obtain approval and connect each action to an observed condition.
| Evidence | Next check |
|---|---|
| New exception with the same request ID | Failing code path, input shape, release, and exception chain |
| 500 starts at one release boundary | Code, configuration, schema, and dependency compatibility for that release |
| Missing configuration or secret reference | Presence, access, format, version, and load path without printing the value |
permission denied or policy denial |
Runtime identity, target path, parent traversal, SELinux/AppArmor, and mount mode |
| Process exit, OOM kill, disk-full, or file-descriptor error | Runtime and operating-system resource evidence |
| Database authentication, connection, pool, or schema error | Database health, credentials reference, network path, pool state, and migration state |
| Dependency timeout or rejected response | Calling-service deadline, dependency logs, gateway status, and retry behavior |
| Serialization or template exception after work completes | Whether side effects committed before response generation |
| Edge reports 500 but origin has no matching request | CDN, WAF, load balancer, ingress, or edge function |
| Invalid input consistently triggers 500 | Validation bug and a more accurate 4xx response |
Reproduce only a safe request
Reproduce a known read-only GET once and discard the body. Avoid verbose client output because it can reveal authorization and cookie headers.
request_time="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
curl --silent --show-error \
--dump-header - \
--output /dev/null \
--write-out 'status=%{http_code} total=%{time_total}s\n' \
'https://example.com/failing-read-only-page' |
grep -Ei '^(HTTP/|server:|via:|x-request-id:|request-id:|status=)'
printf 'request_time=%s\n' "$request_time"
This allowlist prints only the response status line, common routing clues, and common request-ID headers. Header names differ between services, so adjust the allowlist to the documented non-secret fields rather than dumping every header. Do not persist cookies, authentication challenges, signed links, or internal routing values in an incident artifact.
HEAD is not guaranteed to execute the same application path as GET. Use it only when the service documents equivalent handling. Never replay a production POST, PUT, PATCH, or DELETE merely to obtain another log line. A 500 response does not prove that the operation was not committed.
If the failing request requires authentication, use the organization's approved test identity and secret injection method. Do not paste a bearer token, session cookie, API key, password, or signed URL into shell history, tickets, or shared logs.
For a non-idempotent request, prefer existing logs and a sanitized fixture in a non-production environment. If production reproduction is unavoidable, define the expected side effect, duplicate-prevention key, cleanup owner, and authorization before sending it.
Identify which layer emitted the 500
A modern request can cross a CDN, WAF, load balancer, ingress, reverse proxy, application, runtime, database, and external service. The response body's branding or Server header is only a clue; intermediaries can replace both.
Work inward with one timestamp and request ID:
- Find the request in the outermost edge or gateway access log.
- Find the same ID in the origin proxy or ingress log.
- Find it in the application request and exception logs.
- Check runtime and operating-system events around the same process and time.
- Follow explicit dependency identifiers into database, queue, cache, or upstream logs.
If the edge has a 500 but the origin has no matching request, investigate edge policy, functions, routing, and origin connectivity. If the reverse proxy records 500 and its existing log format includes an upstream status, compare final status with upstream status. If the application records an exception for the same request ID, the proxy is probably reporting an application failure rather than causing it.
Absence of a log entry is useful only after confirming that logging was enabled, the correct instance and time range were queried, the buffer flushed, the clock was synchronized, and the event was retained. A missing row is not proof that a component was never reached.
Separate symptom, trigger, and root cause
These three concepts prevent weak conclusions:
- Symptom: the client received HTTP 500.
- Trigger: a particular request, input shape, release, configuration path, or resource state exposed the failure.
- Root cause: the defect or condition that made the server unable to handle that trigger correctly.
For example, a large request may trigger an unhandled serialization exception. The request size is the trigger; missing validation or unsafe serialization is the root cause. Calling the input “the cause” can lead to blocking legitimate traffic while leaving the bug in place.
A deployment boundary is also correlation, not proof. Confirm the failing component, exception, incompatible configuration, schema state, or dependency behavior before assigning causality.
Branch by the evidence
Deployment or configuration mismatch
Compare the failing instance's exact release, startup output, loaded configuration identifiers, feature flags, dependency versions, and schema expectations with a healthy instance. Check whether every required key exists and parses, but never print secret values to prove presence.
Configuration can be present but unusable: wrong encoding, extra whitespace, expired certificate, inaccessible file, invalid URL, or a value mounted into a different path. Validate structure and access with redacted diagnostics. If only some instances fail, compare their immutable release and configuration identifiers rather than manually patching one host.
Missing or inaccessible secret
Logs should name the missing reference, not its value. Confirm that the workload identity can access the intended secret version and that the application loaded it. Do not rotate or re-create a secret until evidence shows the secret itself is invalid; an access-policy, mount, name, or reload problem can produce the same symptom.
After any approved secret change, verify both successful access and absence of the secret from process arguments, environment dumps, exception text, and logs.
Filesystem or mandatory-access-control denial
Use the exact path and operation from the error. Check the runtime user, file ownership and mode, search permission on every parent directory, mount options, and SELinux or AppArmor denials. Do not use recursive ownership changes, 777, or disabled mandatory access control as diagnosis.
A write failure may concern a cache, upload, temporary directory, session store, socket, or log path rather than application code. Give the process only the access its documented operation requires.
Runtime or host resource failure
Look for process exits, OOM-kill records, allocation errors, exhausted file descriptors, thread or worker failures, full filesystems, exhausted inodes, and container limit events. Confirm whether the process stayed alive and whether only one instance was affected.
Resource pressure is not automatically the root cause. A memory leak, unbounded query, request amplification, stuck dependency, or incorrect limit can be the underlying defect. Increasing capacity without understanding the growth pattern may only postpone recurrence.
Database failure
Correlate the application error with database availability, authentication result, connection-pool state, TLS or DNS failure, transaction result, query error, and schema version. Avoid logging full queries when they contain customer data, and do not expose connection strings.
Determine transaction state before retrying. A database commit can succeed even when the application later fails to serialize a response. Use application-level idempotency and operation lookup rather than assuming HTTP 500 means nothing changed.
Upstream service or queue failure
Record the dependency name, operation, outcome class, and correlation ID. Distinguish connection refusal, invalid protocol response, timeout, explicit 4xx/5xx, and client-side cancellation. Check the dependency's own logs rather than treating the caller's wrapper exception as final proof.
Review deadlines and retries together. Uncoordinated retries across clients, proxies, and application libraries can multiply load. A mitigation that changes a timeout or retry policy is state-changing and requires a bounded test and approval.
Serialization, rendering, or error-handler failure
A handler can complete business work and then fail while encoding JSON, rendering a template, or writing the response. Capture the exception type and stage. Before retrying, verify whether the original operation committed.
The global error handler can also fail. Keep the fallback response small, dependency-free, and tested. Do not let an error page query a database, call another service, or render user-controlled details when a static response is sufficient.
Routing or middleware failure
Confirm the normalized route, matched handler, middleware order, authentication context, and rewrite chain. One route can resolve to the wrong handler after a deployment while neighboring paths work. Preserve both the external path and internal route name; avoid logging sensitive query values.
When invalid client data reaches a code path that throws, add validation and return the applicable 4xx response. Do not hide the application defect by keeping a blanket 500 while silently discarding the input.
Use the most accurate HTTP status
HTTP 500 is correct for an unexpected server condition when a more specific status does not fit. It should not replace information the server already has:
| Status | Use when |
|---|---|
| 400 | The server cannot process the request because of malformed syntax, framing, or other perceived client error |
| 401 | The request lacks valid authentication credentials and the response includes an applicable challenge |
| 403 | The server understood the request but refuses it |
| 404 | The target resource is not found or is intentionally not disclosed |
| 409 | The request conflicts with the current resource state |
| 415 | The request content format is unsupported for the method and resource |
| 422 | The content syntax is valid but its instructions cannot be processed |
| 502 | A gateway or proxy received an invalid response from an inbound server |
| 503 | The server is temporarily unable to handle the request, such as overload or maintenance; Retry-After may be appropriate |
| 504 | A gateway or proxy did not receive a timely upstream response |
The definitions come from RFC 9110's client-error and server-error sections. The component's role matters: an application depending on a database is not necessarily acting as an HTTP gateway, while a reverse proxy waiting for an upstream server is.
Do not remap every exception to a specific status without preserving the internal error category. Accurate status codes improve client behavior, but they do not remove the need to fix an application bug.
Return a generic response and an internal request ID
Unexpected errors should not expose stack traces, source paths, query text, environment variables, dependency addresses, framework versions, or secret values. A small API response can be enough:
{
"error": "Internal server error",
"request_id": "req_example_7f3a"
}
The request ID must be safe to disclose, unique enough for correlation, and mapped to internal records. Validate or replace client-supplied correlation values so attackers cannot inject delimiters or impersonate another request. Do not encode account IDs, email addresses, or other personal data into the identifier.
The OWASP Error Handling Cheat Sheet recommends a centralized error-handling approach that returns generic information while recording useful internal details. Test the handler itself and every response format your service advertises.
Log one useful, sanitized event
This synthetic example shows a framework-neutral event shape. Runtime values, field names, and nesting will vary:
{
"timestamp": "2026-09-15T14:32:08.412Z",
"severity": "ERROR",
"service": "orders-api",
"environment": "production",
"release": "2026.09.15.3",
"request_id": "req_example_7f3a",
"http": {
"method": "POST",
"route": "/orders",
"status_code": 500
},
"error": {
"type": "ResponseSerializationError",
"code": "ORDER_RESPONSE_INVALID"
},
"dependency": {
"name": "orders-db",
"outcome": "ok"
},
"duration_ms": 1842
}
Use a normalized route such as /orders, not a full URL containing customer or token values. Store a sanitized stack trace separately when it is necessary and access-controlled. Do not record passwords, access tokens, session identifiers, encryption keys, database connection strings, payment data, or sensitive request bodies.
All external event values are untrusted. Remove or encode carriage returns, line feeds, delimiters, and control characters to prevent log injection. Preserve structured types rather than embedding JSON inside a message string. The OWASP Logging Cheat Sheet also covers event confidence, time synchronization, access control, integrity, retention, and logging-system failure.
Alert on a rate, not only a raw 500 count
A raw count lacks traffic context. Ten failures among ten requests and ten failures among a million requests describe different conditions. Calculate an error ratio from the same observation boundary:
500 error ratio = responses with status 500 / all completed responses
Dimension the signal by service, normalized route, environment, region or cluster, and release where those fields are reliable. Combine a ratio threshold with a minimum request volume so one low-traffic failure does not create a misleading percentage. Keep a separate low-volume detector for critical routes where one failure matters.
Count each request once. If the edge, proxy, and application all record the same 500, deduplicate by request ID or choose one authoritative response log for the alert. Preserve the other layers for diagnosis.
Alert on sustained impact and on clearly new failure signatures, but avoid a unique alert for every stack frame or user-specific URL. Group by stable error code, exception type, service, route, and release. Route alerts to an owner with a link to the exact time window and evidence, not a dashboard without context. Log anomaly detection can supplement explicit error-rate rules when a known fixed threshold does not capture a new pattern.
Make one approved mitigation and verify it
Mitigation may involve configuration, capacity, feature state, dependency routing, credentials, permissions, or code. Every such action changes state and requires the service's approval process.
Before acting:
- Preserve the request and error evidence.
- State the hypothesis that the action tests.
- Change one variable with a defined owner and scope.
- Observe the same route, service, release, and dependency boundary.
- Verify the original safe request and a nearby control request.
- Confirm both the 500 error ratio and request latency after the change.
- Check that a security or validation control was not accidentally weakened.
A lower 500 count can simply reflect lower traffic. Use both numerator and denominator, and compare completed requests, error categories, latency, saturation, and dependency outcomes. Do not declare resolution from one successful refresh.
For an active incident, Live Tail incident-response guidance provides a compact workflow. For general evidence handling, see how to read logs systematically.
Centralize 500 evidence without inventing fields
Centralization helps correlate edge, proxy, application, runtime, and dependency records after local files rotate. A collector should buffer appropriately, protect transport, and preserve timestamps and request IDs. Its parser and mapping determine which fields become searchable; validate the final record with a non-sensitive test error in a controlled environment.
Fluxtail log management is a paid, self-service logs-focused destination with Starter and Pro plans. After a separately configured collector sends events through a documented receiver, Fluxtail provides named streams, Live Tail, alerts, and search filters for fields that actually arrive. It cannot infer missing release, route, or request-ID fields that the source and collector did not preserve.
Fluxtail's built-in AI chat and hosted MCP endpoint are separate investigation interfaces. Hosted MCP uses account-bound OAuth with PKCE. Its operator mutations are proposed first and require a short-lived confirmation before application. Keep raw events as evidence, scope AI or agent work to the affected service and time window, and verify conclusions against those rows.
If centralized 500 investigation fits the operating model, create a Fluxtail account, configure a supported collector path, and verify one sanitized test event end to end.
Server error 500 checklist
- Treat 500 as a symptom, not a root cause.
- Give visitors a request ID and generic response, never an internal stack trace.
- Do not replay a non-idempotent production request until its outcome is known.
- Correlate edge, origin, application, runtime, system, and dependency evidence.
- Follow the exact exception or system event instead of restarting by habit.
- Distinguish the triggering request or release from the underlying defect.
- Return 4xx, 502, 503, or 504 when those semantics accurately describe the known condition.
- Keep tokens, sessions, personal data, connection strings, and raw bodies out of logs.
- Sanitize untrusted values to prevent log injection.
- Alert on an error ratio with total request volume and stable service dimensions.
- Change one approved variable, then verify the original request, error ratio, latency, and access controls.