Learning objectives
After reading this article you will be able to:
- Tell 500, 502, 503 and 504 apart from the exact symptom
- Find the cause in your app logs and proxy logs
- Fix the most common causes and keep them from coming back
Every status code from 500 to 599 says the same thing: the request was valid, but the server could not complete it. The visitor did nothing wrong, and the fix is on your side. The four codes you will meet most often look similar in the browser but point to very different places in the request chain.
The trick is to find out who produced the error. A bare "500 Internal Server Error" page from your framework means your code failed. A "502 Bad Gateway" page, plain or branded by nginx, a CDN or a load balancer, means the proxy in front of your app is complaining about the app.
What does each 5xx code mean?#
| Code | Name | Raised by | Plain meaning |
|---|---|---|---|
| 500 | Internal Server Error | The app | An unexpected condition stopped the app from answering |
| 502 | Bad Gateway | A proxy or gateway | It received an invalid response from the upstream server |
| 503 | Service Unavailable | App or proxy | Temporarily unable to handle the request (overload or maintenance) |
| 504 | Gateway Timeout | A proxy or gateway | It did not get a timely response from the upstream server |
These definitions come from RFC 9110, section 15.6. The distinction that matters: 502 and 504 are only produced by something sitting between the visitor and your app. With nothing in front of your app, you will rarely see them, unless your own code acts as a gateway to another service.
Why do I get a 500 Internal Server Error?#
A 500 is the catch-all. Your code threw an exception that nothing handled, and the framework answered with 500. The browser tells you nothing useful; the log tells you everything.
Common causes, from most to least frequent:
- An unhandled exception: reading a property of
undefined, a failed JSON parse, a missing field. - A failed dependency: the database is unreachable, credentials are wrong, a third-party API timed out.
- A missing environment variable or config file that works on your machine but not in production.
- Running out of memory, which kills the process mid-request.
- A bad deploy: a missing module or a build that did not finish.
To diagnose, reproduce the request and read the stack trace in the app logs. Then make sure errors are caught and logged instead of leaking out to the visitor:
import express from "express";
const app = express();
app.get("/users/:id", async (req, res) => {
const user = await findUser(req.params.id); // may throw
if (!user) return res.status(404).json({ error: "not found" });
res.json(user);
});
// Express 5 forwards rejected promises from async handlers here
app.use((err, req, res, next) => {
console.error(`${req.method} ${req.url}`, err);
res.status(500).json({ error: "internal error" });
});
app.listen(process.env.PORT ?? 3000, "0.0.0.0");Note the 404 for a missing user. Returning 500 for a normal "not found" case floods your logs and hides real failures.
Why do I get a 502 Bad Gateway?#
A 502 means the proxy reached out to your app and got garbage, or nothing. As MDN puts it, a server acting as a gateway received an invalid response from the upstream server. In practice the app is almost always down or unreachable. Check these in order:
- The app crashed or is not running. The proxy has nothing to talk to. Look at the process: did it exit, loop on restarts, or get killed for memory?
- The app is still starting. Right after a deploy or restart, the port is not open yet. Slow boots (migrations, large model loads) make this last longer.
- Wrong port or address. The proxy forwards to port 3000 but the app listens on 8080. Or the app binds to
127.0.0.1inside a container, so nothing outside can reach it. Bind to0.0.0.0and read the port from the environment. - The app closed the connection mid-response, for example a crash during a request or a keep-alive timeout shorter than the proxy's.
- A proxy rule points at the wrong upstream, such as a typo in a
proxy_passor a stale backend address.
If your app works when you open its port directly but fails behind the proxy, the problem is in the proxy config, not in the code. See what a reverse proxy does for how that hop works.
Why do I get a 503 Service Unavailable?#
A 503 says "not right now". Per RFC 9110, the server is temporarily unable to handle the request because of overload or scheduled maintenance, and it may send a Retry-After header to say when to try again (section 10.2.3).
Typical causes:
- Every worker or connection slot is busy, so new requests are rejected or queued out.
- No healthy backend is left: a load balancer marked all of them as failing health checks.
- A deliberate maintenance mode.
- A rate limiter or upstream service shedding load.
If you return 503 on purpose, say when to retry, so crawlers and clients back off politely:
app.use((req, res, next) => {
if (process.env.MAINTENANCE === "1") {
res.set("Retry-After", "600"); // seconds
return res.status(503).send("Back in about 10 minutes");
}
next();
});To fix real overload, find the bottleneck first: CPU pegged, memory full, a slow query holding every connection. Adding workers does not help if the database is the limit.
Why do I get a 504 Gateway Timeout?#
A 504 means the proxy connected to your app, but the app took too long to answer. The app is alive, just slow. Nginx, for example, waits 60 seconds by default between two successive reads from the upstream (proxy_read_timeout 60s), per the nginx proxy module docs. Other proxies and CDNs have their own limits, often shorter or fixed.
Typical causes:
- A slow database query, a missing index, or a lock.
- A request that calls another API which hangs, with no timeout of your own.
- Heavy work done inside the request: image processing, report generation, big exports.
- A saturated app where requests sit in a queue before being served.
The fix is almost never "raise the proxy timeout". Move long work out of the request, answer quickly with a job id, and let the client poll or receive a webhook. Add explicit timeouts to every outbound call so a hung dependency fails fast with a clear error. If you do need a longer limit for one route, raise it for that route only. To understand the layer that spreads requests across instances and can also cut them short, see what a load balancer is.
How do I diagnose a 5xx error step by step?#
- Identify who answered. Check the response headers and the page body. A
Serverheader or a CDN-branded error page tells you which layer failed. - Read the app logs at the time of the error. A stack trace means 500. No trace and no request at all means the proxy never reached the app. The guide on how to read your app's logs helps with this step.
- Check the process. Is it running, how long has it been up, did it restart recently, how much memory does it use?
- Call the app directly, bypassing the proxy, with
curl -i http://localhost:3000/health. If this works, look at the proxy config. - Check dependencies. Database, cache and external APIs. A slow or dead dependency produces 500, 503 and 504 alike.
- Look at the pattern. One route points to a code bug. Every route points to a dead app or proxy. Intermittent errors under load point to capacity or timeouts.
A /health endpoint that checks the database and returns 200 or 503 saves time here, and load balancers can use it to pull a failing instance out of rotation. For a wider checklist when the whole site is unreachable, see why is my website down.
How do I prevent 5xx errors?#
- Log every unhandled error with the request path and a request id.
- Set timeouts on every database and HTTP call your app makes.
- Restart the process automatically if it exits, and alert on repeated crashes.
- Load test before launch to find where capacity runs out.
- Run migrations and slow startup work before the app accepts traffic, not during.
- Track the share of 5xx responses over time. A rising line warns you before users complain.
How to do this on Square Cloud#
On Square Cloud, each web application runs in its own isolated container, and a 5xx investigation starts the same way: with the logs. They stream in real time in the dashboard, the CLI, the VS Code extension and the mobile app, which makes a crash or stack trace easy to catch right after a request fails.
A 502 after a deploy usually means the app is not listening on 0.0.0.0 on the port in the PORT variable, so check that first. Containers are always on, with no cold start or sleep mode, and automatic restart (on by default for dashboard deploys) brings back an application that exits with an error. Running out of memory is different: the application stops and stays stopped until you raise its RAM in the settings and start it again. Web applications need at least 512 MB of RAM.
To see how often errors happen, the web analytics included in every plan show routes and status codes, collected on the server. Pro and Enterprise plans add p50, p95 and p99 latency and HTTP logs, useful for spotting the slow routes behind 504s. Your site also gets a *.squareweb.app subdomain with HTTPS, so you can test the app directly before attaching a custom domain.
The site hosting features are on the Sites page.
Website hosting
Static and SSR sites with automatic TLS and a global CDN
FAQs
What is the difference between a 502 and a 504 error?
A 502 means the proxy got an invalid response (or none at all, because the connection was refused or dropped). A 504 means the proxy connected but the app did not answer in time.
Is a 5xx error my fault or the visitor's fault?
The 5xx class means the server failed to handle a valid request, so the cause is on the server side. Reloading, clearing cookies or switching browsers rarely helps.
Does a 503 error hurt SEO?
A short outage usually does little harm: Google slows crawling when it sees 5xx errors and keeps already indexed URLs for a while. URLs that keep returning a server error are eventually dropped from the index.
Why do I get a 502 right after deploying?
Usually the app crashed on startup, is still booting, or listens on a different port or address than the proxy expects. The app logs show which one.
More in Websites
See all 20 articles- What is a static site (and how to host one)A static site is a set of ready-made HTML, CSS and JavaScript files served the same to every visitor. Learn how it works, when to use one and how to host it.
- How much does it cost to host a website?Hosting a website costs anywhere from nothing to a few dozen dollars a month. See what goes into the bill: hosting, domain, traffic and extras, with real limits.
- How much does static site hosting cost?Hosting a static site costs between zero and a few dozen dollars a month in most cases. See how traffic sets the price, with step-by-step math.
- How much does it cost to host a Next.js app?Hosting a Next.js app costs from zero with a static export to tens of dollars a month with a server. See the billing models, traffic math and hidden costs.