Each 5xx code names a different layer, and that is the whole diagnostic value. 500 means the application ran and failed. 502 means the proxy reached the application and got back something it could not use. 503 means there was nothing available to ask in the first place. 504 means the application was asked, said nothing, and ran out of time. Before you read a single line of application code, the number already tells you which process to look at.
The one-line distinction
| Code | Who generated it | What happened | First place to look |
|---|---|---|---|
| 500 | Usually your application | Code raised an unhandled error | Application error log |
| 502 | The proxy or web server | Upstream replied with garbage, or died mid-reply | PHP-FPM / app process log |
| 503 | The proxy or web server | No upstream available — down, full, or deliberately stopped | Process status and pool limits |
| 504 | The proxy or web server | Upstream accepted the request and never answered in time | Slow queries, external API calls |
Notice that only 500 typically comes from your code. The other three are the front-end server describing a problem it had with the back-end, which is why grepping your application for the string "502" finds nothing and confuses everybody on their first outage.
500 Internal Server Error — the application spoke and it was bad news
A 500 means the request got all the way through and something threw. A missing environment variable, a database connection refused, a PHP fatal error, an unhandled exception four frames deep.
The important property of 500 is that something is running. The stack is alive; the code is not happy. That makes it the easiest of the four to debug, because the error log almost always contains a stack trace with a file and line number.
Where people lose time is looking in the wrong log. In a typical PHP stack the trace is in the PHP-FPM pool log or the application's own log, not the web server's access log, and if display_errors is off — as it should be in production — the browser shows a blank page or a generic message while the real error sits in a file nobody opened. If you are not sure where yours writes, our guide to PHP error handling and logging covers the settings that decide it.
One exception worth knowing: the web server itself can emit 500 without your code running at all, most often when a rewrite rule is malformed or an .htaccess directive references a module that is not loaded. If your application log is completely silent during a 500, suspect the configuration rather than the code.
502 Bad Gateway — the answer came back broken
502 means the proxy connected to the upstream, sent the request, and got a reply it could not parse — or got the connection closed underneath it. In practice, on a PHP stack, it means the FPM worker handling your request died.
Common causes, roughly in order of how often they turn out to be the culprit:
- The worker was killed. Memory limit exceeded, or the kernel OOM killer chose it. The tell is that the application log ends mid-request with no error — the process did not get the chance to write one.
- A segfault in an extension. Rarer, but it produces exactly the same silence. Check the system log for the crash.
- Socket path mismatch. The web server is talking to a socket the pool no longer listens on, typically after a PHP version change. This one gives you 502 on absolutely every request, which is a useful signature — real crashes are usually intermittent.
- Response too large for the proxy buffers. A big response with buffering configured too small produces a 502 that reproduces reliably on one URL and never on others.
The diagnostic that settles it in ten seconds is whether the failure is total or partial. Every request failing points at configuration. One in fifty failing points at a crash. If you are in the middle of one now, the full breakdown lives in every possible cause of a 502 Bad Gateway.
503 Service Unavailable — there was nobody to ask
503 is the one people most often misread, because it looks like a crash and usually is not. It means the front-end had no healthy upstream to hand the request to. Three quite different situations produce it:
The backend is stopped. PHP-FPM is not running, the Node process exited, the container is down. Straightforward, and systemctl status or docker ps answers it immediately.
The backend is full. Every worker in the pool is busy, the queue is at its limit, and new requests are refused rather than queued forever. This is the interesting case: nothing has crashed, nothing is in the error log, and the site is down anyway. It is a capacity problem wearing an error's clothing, and adding workers without finding out why they are all busy usually just moves the wall.
Somebody put the site in maintenance mode. 503 is the correct code for planned downtime, which is why a well-behaved maintenance page returns it rather than a 200. Search engines treat a 503 with Retry-After as temporary and keep the page in the index; a maintenance page served with 200 teaches them that "We'll be back soon" is your content.
So the first question with a 503 is not "what crashed" but "is anything listening, and if so, is it busy". Those have opposite fixes.
504 Gateway Timeout — the answer never came
504 means the upstream took the request and exceeded the proxy's patience. Nothing is broken in the sense of being crashed; something is slow.
Almost always one of three things: a database query with no usable index, an outbound call to a third-party API that is itself timing out, or a genuinely long job — an import, a report, a video conversion — running inside a web request where it does not belong.
The temptation is to raise the timeout. That is occasionally right and usually a way of converting a fast failure into a slow one, while holding a worker hostage for the duration and pushing you toward the 503 above. If a request legitimately needs sixty seconds, it belongs in a queue with the browser polling for the result, not in a socket somebody is waiting on.
A four-step routine that works every time
- Reproduce with headers.
curl -sSI https://example.com/path— confirm the code and see who claims responsibility in theServerheader. - Ask the application directly, bypassing the proxy. If the app answers 200 on its local port while the public URL returns 502, the problem is between them, not inside them.
- Read the upstream log, not the front-end log. The web server error log tells you it could not get an answer; the application or FPM log tells you why.
- Check whether it is total or intermittent. Total means configuration. Intermittent means resources — memory, workers, or a slow dependency.
And the other question people ask: 4xx or 5xx?
The dividing line is responsibility. 4xx says the request was wrong — bad syntax, missing authentication, a path that does not exist, a payload the endpoint will not accept. Repeating it unchanged will fail again. 5xx says the request was reasonable and the server failed to fulfil it; the same request may well succeed a minute later.
That distinction matters far beyond pedantry, because it drives behaviour you do not control. Retry logic in client libraries retries 5xx and gives up on 4xx. Monitoring systems page a human for 5xx and ignore 4xx. Search engines drop pages that return 4xx and keep pages that return 5xx. An API that answers 500 when the client sent malformed JSON will get that malformed request retried forever, and the alert will wake somebody up.
Frequently asked questions
What is the difference between a 500 and a 502 error?
A 500 is generated by the application, which ran and threw an error — there is normally a stack trace in its log. A 502 is generated by the proxy or web server, which reached the application but received an unusable reply, most often because the worker process died mid-request.
What is the difference between 502 and 503?
502 means the upstream was reachable and answered badly. 503 means there was no healthy upstream to answer at all — the process is stopped, every worker is busy, or the site is deliberately in maintenance mode.
Which status code should a maintenance page return?
503 Service Unavailable, together with a Retry-After header giving either a number of seconds or an HTTP date. This tells search engines the outage is temporary and keeps the page in the index. A maintenance page returning 200 risks the placeholder being indexed as the page's real content.
Does a 504 mean my server is down?
No. It means the upstream accepted the request and did not respond within the proxy's timeout. The process is running; something in the request path is too slow — commonly an unindexed database query, a slow third-party API call, or a long job running inside a web request.
Should my API return 4xx or 5xx for invalid input?
4xx. Invalid input is a client error, and client libraries will not retry a 4xx. Returning 5xx for malformed input causes automatic retries of a request that can never succeed, and triggers alerting intended for genuine server failures.