500
The application itself ran, hit something it did not expect, and gave up — so unlike a 502 or a 504 there is a stack trace somewhere, and the whole job is finding which log holds it.
The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.
Why you are seeing it
It is one endpoint rather than the whole site, and it usually correlates with a payload rather than with a moment: the same page works until a particular record, a particular file size or a particular character shows up. The response body is your API's fault handler or a framework error page, not the proxy's, so `res.json()` may parse successfully and hand you an error object that looks like data — or it may throw `Unexpected token '<'` when the framework fell all the way back to HTML. Either way the browser has no more information than the three digits, because the exception stayed on the server.
What to do
Capture the request that caused it, not the error: the exact method, URL, headers and body, because the back end will ask for a request that reproduces it and yours is the only copy. Then read the response headers for a correlation id — `x-request-id`, `x-amzn-trace-id`, `cf-ray` — and hand that string over, since it is the one value that finds the matching server log line in seconds. Check `res.ok` and `content-type` before parsing so a stray HTML error page does not turn into a parser bug three frames away from the real one. And do not retry automatically: a 500 gives you no way to tell whether the write happened, so a retried order can become two orders. If the endpoint must be retryable, send an idempotency key and let the server deduplicate.
Why you are seeing it
Your framework's last-resort handler caught something no other handler claimed, and turned it into three digits before the response left the process. The exception, the stack and the offending input are all in your log, and none of them are in the response — which is correct, because leaking them is how a 500 becomes an information disclosure. The families are few: an unhandled exception on a specific input, a dependency that stopped answering (a connection pool exhausted, a database in recovery, an expired credential), or a process that started fine and is now missing an environment variable it only reads on the first request of a particular path.
What to do
Start from the correlation id and read the first exception in the trace rather than the last, because the last frame is usually your own error handler failing on the real error. If nothing is in the application log, the 500 was not written by the application: Apache's error log reading "Premature end of script headers" points at a CGI that printed before its headers or at a suexec permission refusal, and nginx writes its own 500 for a rewrite or internal redirection cycle, which is a configuration bug and not a code bug. Then reproduce with the exact payload rather than a simplified one, since the input that triggers a 500 is nearly always the one nobody thought was possible. Make the fix a 4xx where the input really was invalid — a validation failure that arrives as 500 is your error handling being wrong twice, once for crashing and once for blaming the server. Finally, check what `proxy_next_upstream` is set to before you assume one 500 meant one failed request: nginx's `http_500` is an opt-in value, so with it enabled a single client request can be replayed against every upstream in the group.
Why you are seeing it
Nothing you did caused it and nothing you can do locally will change it: the website's own program ran into a fault while building your page. It generally affects one action rather than the whole site — the search box works, the checkout does not — and it can be triggered by something specific about your request, such as an unusual character in a form field or a file that is larger than the site expected.
What to do
Reload once, since a genuinely transient fault clears on its own. If it does not, the useful thing is to stop repeating the same action, especially if it involved a payment, a booking or a submission: a server error hides the answer but does not necessarily undo the work, so pressing the button again can do it twice. Check your order history, inbox or account page before retrying. When reporting it, include the exact time, the page address, and what you had just entered — the site's operators can find the matching log entry from that, and the field you typed is very often the trigger. Clearing cookies, changing browser or restarting the router will not help, because the failure happened on the other end of the connection.
Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.
curl -sS -o /dev/null -D - -w '\n%{http_code} in %{time_total}s\n' -H 'content-type: application/json' --data '{"quantity":-1}' https://example.com/api/orders`-sS` silences the progress meter while keeping error messages, `-o /dev/null` throws away the body, `-D -` dumps the response headers to stdout, `--data` sends the payload and makes the request a POST, and `-w` prints the status and the wall-clock duration. Read the header block first, because it answers the only question the status line cannot: which layer wrote this. A `server:` naming your application framework, or any `x-request-id` your stack sets, means the request reached your code and the exception is in your log under that id. A `server: nginx` or `server: cloudflare` with no application headers at all means you are looking at an error page from in front of the application, and the three digits are worth re-reading — an intermediary that could not get a usable answer reports 502, not 500. The elapsed time is the second tell: an application-level 500 usually returns as fast as a success, because the exception was thrown rather than waited for, so a 500 that takes thirty seconds is a timeout inside your code and points at the dependency that hung rather than at the line that threw. Swap the payload for one you know is valid to confirm the endpoint itself works, and keep the failing one — it is the reproduction, and it is the thing the back end cannot recover from a log.
RFC 9110 §15.6.1 defines 500 as the server having encountered an unexpected condition that prevented it from fulfilling the request, sitting inside a 5xx class §15.6 introduces as the server being incapable of performing the request. The definition is deliberately the least specific one in the specification, and that is the useful part: 500 is an admission rather than a diagnosis, so the code carries no information at all about what broke, only about who noticed. Three consequences follow. First, a 500 your application produced is a perfectly valid HTTP response, so a proxy in front of it relays the response untouched instead of substituting one of its own — which is exactly why RFC 9110 §15.6.3 reserves 502 for the case where the intermediary received an invalid response, and why a 500 is evidence that your code ran while a 502 often means it never did. Second, 500 is absent from the list of heuristically cacheable status codes in RFC 9110 §15.1 — which names 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501, and nothing else — so no intermediary will store one on its own initiative and a 500 that persists is being regenerated on every request rather than served from a cache. Third, 500 says nothing about whether the request had an effect: the specification gives the client no way to know how far the server got before the unexpected condition, which is why a 500 on a payment or an order is not automatically safe to retry. Servers also emit 500 from their own machinery, not only from application code — Apache's CGI documentation states that an "Internal Server Error" whose error log reads "Premature end of script headers" means the script emitted something before its HTTP headers, or that suexec's permission checks refused it, and nginx answers 500 for its own internal faults such as a rewrite or internal redirection cycle. In both cases the three digits are the same and the log line is the entire content.
| Confused with | How to tell them apart |
|---|---|
| 502 | 500 is the application admitting its own failure; 502 is an intermediary reporting that the application never handed it a usable answer. RFC 9110 §15.6.3 defines 502 as an invalid response received from an inbound server, and an application's own 500 is a valid response, so a proxy passes it straight through. Practically: a 500 means there is a stack trace to find, while a 502 often means the worker died before any handler could run and the application log is empty. |
| 503 | 503 is a decision and 500 is an accident. RFC 9110 §15.6.4 defines 503 as the server being temporarily unable to handle the request due to overload or scheduled maintenance, and lets it carry `Retry-After` to say for how long. Nothing in 500 suggests waiting, because nothing about it is expected to pass. If your service sheds load by throwing, it is reporting a planned refusal as a crash, and every client backoff heuristic keyed on 503 will miss it. |
| 400 | RFC 9110 §15.5.1 defines 400 as the server being unable to process the request due to something perceived to be a client error. That is the honest code for input your endpoint rejects. A 500 raised while validating input is a bug in the error handling rather than in the server: the request was answerable, the answer was "no", and the 5xx tells the client to retry something that will never succeed. |
Cloudflare 520 | Through Cloudflare the two are easy to tell apart, and the difference is worth knowing. Cloudflare documents 520 as the origin returning an empty, unknown or unexpected response — including headers above its 128 KB limit — so a 520 means your origin's answer was not usable HTTP. A 500 arriving through Cloudflare is the opposite: a well-formed response your application produced deliberately, which Cloudflare relayed untouched. |
501 | RFC 9110 §15.6.2 defines 501 as the server not supporting the functionality required to fulfil the request, and names it the appropriate answer when the server does not recognise the method. Unlike 500 it is a statement about the server's capabilities rather than about one request going wrong, and it is one of the few error codes RFC 9110 §15.1 lists as heuristically cacheable — so a mistaken 501 can be stored by a cache and repeated after the cause is gone, while a 500 cannot. |
All of these run in your browser — nothing is uploaded.
Codes people usually end up reading in the same session as 500.
Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request with --resolve example.com:443:203.0.113.10, which connects to the origin address you name while still sending the original host name and SNI — if the answer changes, the edge and the origin disagree.
No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.
Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.
Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.
Still stuck on 500? browse every status code in the reference — or go back up to the block written for your side of the stack.