A status code is the first thing a failing request tells you, and it narrows the search more than people give it credit for: a 502 and a 504 look identical in the browser and point at two different problems. This page is the long version of that idea, one row per code with the situation it really appears in, and a fix where there is a standard one.
How this reference is organized
The first digit is the contract. 1xx are interim answers, 2xx success, 3xx redirection, 4xx blames the request, 5xx blames the server. RFC 9110 defines the core set; the rest comes from smaller RFCs (6585 for 429 and 431, 7725 for 451, 8297 for 103) and the IANA registry that tracks them all. Codes that no spec defines, like the nginx pair 444 and 499 and Cloudflare's 52x range, sit in their own group and carry a non-standard badge, because treating them as HTTP proper is how people end up searching the RFCs for a code that only exists in one vendor's logs.
Every row is an anchor: #404 in the URL scrolls to and highlights the code, and the small button on each row copies that link. The filter box matches against the number, the name and the description text, so "timeout" finds 408, 504, 522, 524 and 599 at once. The three boxes between the rows cover the pairs that get mixed up in practice: 401 against 403, the five redirect codes, and the 502/503/504 triple.
Seeing the status code
In the browser, the Network tab of devtools shows a Status column for every request after a reload. Turn on "Preserve log" before reproducing anything involving redirects or navigations, otherwise the interesting entries vanish when the page changes, and turn on "Disable cache" so a 304 or a memory-cache hit does not stand in for the answer the server would actually give. The response headers of a selected request carry the context the number alone lacks: the Allow list on a 405, Retry-After on a 429 or 503, Location on any redirect.
On the command line, curl answers the same questions faster. curl -i https://example.com/api prints the status line and headers above the body. For just the number, curl -s -o /dev/null -w "%{http_code}\n" is the script-friendly form. Redirect chains need -L to follow and -w "%{url_effective}" to show where they end; each hop's code appears with -i as multiple status lines. Be careful with curl -I: it sends HEAD, and enough servers treat HEAD differently from GET that a surprising 404 or 405 from -I deserves a retest with a real GET before you trust it.
Which layer sent the error
The most useful debugging question about any 5xx is not "what does the code mean" but "who produced it". A modern request passes a CDN, a load balancer, maybe an ingress, then the app, and each layer can answer on behalf of the ones behind it. Three clues identify the author. The Server header changes per layer: an error page announcing nginx/1.24.0 when your app runs Express came from the proxy. The body styling gives it away too, since framework error pages, the bare nginx default page and a CDN's branded error page look nothing alike. And some layers use codes nobody else does: anything in the 52x range is Cloudflare talking about your origin, and a 499 exists only inside nginx logs.
Once the layer is identified, the fix has an address. A 502 from nginx means the upstream connection failed, and the nginx error log names the address and the reason, which settles "crashed" versus "wrong port" in one line. A 503 from a load balancer points at health checks, and in Kubernetes specifically at readiness probes taking pods out of rotation while the app itself would answer fine. A 504 means the app holds the request too long, and the log of the layer that timed out says exactly how long it waited. Our experience: raising the proxy timeout is the most common first reaction and almost never the fix, because the user has usually given up before the higher limit is reached anyway.
Status codes in fetch and XHR
fetch resolves for any completed HTTP exchange, including 404s and 500s, and only rejects when no response exists at all: DNS failure, refused connection, abort. Error handling therefore means checking response.ok, which is exactly "status in the 200-299 range", or the status itself. Axios inverts this and rejects on any non-2xx, so the same endpoint appears to behave differently between codebases that use different clients.
Redirects are followed before your code runs: a fetch against a URL answering 301 resolves with the final response, and the only traces are response.redirected and the final response.url. You cannot read the 301 itself from JavaScript without redirect: "manual", and even then the response is opaque. A status of 0, finally, is not a status: an XHR reports 0 and fetch throws when the browser blocked the exchange or the network failed, and with cross-origin requests that usually means the browser refused to share the answer: the server may well have replied 200, but without the matching Access-Control-Allow-Origin the response never reaches your code, and only the browser console names the reason. If the code you are staring at is a 304, that is the caching machinery working as designed, and the Cache-Control analyzer explains which header produced it.
Picking the right status code
What is the difference between HTTP 401 and 403?
401 means the request is not authenticated: credentials are missing, expired or invalid, and the response carries a WWW-Authenticate header saying how to log in. 403 means the server knows who you are and still refuses: the account lacks permission, the API key misses a scope, or a WAF rule fired. The practical test: if logging in (again) could change the answer, the right code is 401; if only granting more rights could, it is 403. When debugging a 401, first confirm the Authorization header actually reaches the app, since Apache strips it for CGI scripts unless CGIPassAuth is enabled and proxies have to be told to forward it.
What is the difference between a 502, 503 and 504 error?
All three are sent by a proxy or load balancer, not by the application. 502 Bad Gateway: the proxy connected to the app and got a refused connection or a broken reply, so the app is down, crashed, or listening on the wrong port. 503 Service Unavailable: something deliberately is not serving, such as maintenance mode or a load balancer whose health checks removed every backend. 504 Gateway Timeout: the app accepted the request and then exceeded the proxy timeout (nginx defaults to 60 seconds), so it is alive but too slow. The code alone tells you where to start: 502 means check the process, 503 means check the health checks, 504 means find the slow query.
When should an API return 422 instead of 400?
Use 400 when the request cannot be parsed at all: malformed JSON, an invalid header, a broken multipart body. Use 422 when the request parsed fine but the content fails validation: a well-formed body with a negative quantity or an end date before the start date. Rails made this split popular and RFC 9110 has since standardized 422 as Unprocessable Content. Clients benefit from the distinction because a 400 points at serialization bugs while a 422 points at user input, but consistency matters more than the exact choice: an API that mixes both for the same failure is worse than one that only uses 400.
How does the Retry-After header work with a 429 response?
Retry-After tells the client when to try again, either as an integer number of seconds (Retry-After: 120) or as an HTTP date (Retry-After: Wed, 21 Oct 2026 07:28:00 GMT). It is defined for 429 and 503 and also allowed on 3xx responses. A well-behaved client sleeps for that duration before retrying; when the header is absent, exponential backoff with jitter is the standard fallback. Many APIs additionally send RateLimit or X-RateLimit-Remaining headers that let you slow down before the 429 ever happens, which is cheaper than reacting to it.
What is the difference between 301, 302, 307 and 308 redirects?
Two axes: permanence and method preservation. 301 is permanent and 302 temporary, and with both, clients are allowed to turn a redirected POST into a GET, which most do. 307 (temporary) and 308 (permanent) forbid that: the client repeats the same method with the same body, which makes them the right choice for redirecting API endpoints, form posts and uploads. 303 is the fifth member and explicitly demands GET, which is the post-redirect-get pattern. Browsers also cache 301 and 308 aggressively, so use the temporary codes while a redirect is still being tested. The SEO side of the choice is a separate question with its own answer.
How do I see the HTTP status code of a page in the browser?
Open devtools (F12), switch to the Network tab and reload the page: the Status column shows the code of every request, with failures in red. Two settings matter: "Preserve log" keeps entries across redirects and navigations, which you need to see a 301 chain, and "Disable cache" avoids 304s and memory-cache hits standing in for the real answer. Clicking a request shows the response headers, where codes like 429 carry the interesting details (Retry-After) next to the number itself.
How do I check an HTTP status code with curl?
The compact form is curl -s -o /dev/null -w "%{http_code}\n" https://example.com, which prints only the number. curl -i shows the status line with all response headers, and adding -L follows redirects, with -w "%{url_effective}" revealing where the chain ended. One trap: curl -I sends a HEAD request, and some servers answer HEAD differently than GET (a few return 404 or 405 for HEAD on routes that serve GET fine), so when a -I result looks odd, repeat it with -sL -o /dev/null -w "%{http_code}" to test the real method.
What is HTTP status 499 in nginx logs?
499 is an nginx-only log code meaning the client closed the connection before nginx could send the response. It never travels over the wire, since there was nobody left to receive it. Isolated 499s are users hitting stop or closing tabs. Frequent 499s on one endpoint mean the caller gives up faster than the upstream answers, commonly a downstream service with a 5 or 10 second client timeout calling an endpoint that takes longer. The fix is on the timing, not the code: speed up the endpoint or align the caller timeout, and check whether the abandoned requests keep running server-side and pile up load.
Why does fetch not throw an error on a 404?
By design, fetch only rejects on network failures: DNS errors, refused connections, aborted requests. A 404 or 500 is a completed HTTP exchange, so the promise resolves and you have to look at response.ok, which is true only for statuses 200 through 299, or at response.status directly. The usual pattern is: if (!res.ok) throw new Error(String(res.status)). Axios behaves differently and rejects on any non-2xx status out of the box, which is why the same API call seems to "fail" in one codebase and "succeed" in another.
What does HTTP 418 "I'm a teapot" mean?
It comes from RFC 2324, the Hyper Text Coffee Pot Control Protocol, an April Fools RFC from 1998: a teapot asked to brew coffee answers 418. It was never part of the HTTP standard, but the code point is treated as taken, most frameworks ship a constant for it, and a 2017 effort to remove it from Node, Go and others was abandoned after community protest. In the wild it appears as an easter egg and occasionally as a deliberate answer to scrapers and bots, precisely because no real client expects it.
How do I fix a 405 Method Not Allowed error?
Read the Allow header of the response first: the server must list the methods the path accepts, and comparing it with what you sent usually ends the search. Typical causes: a POST sent to a route only registered for GET, a missing OPTIONS route (surfaces during CORS preflights), a static file server that refuses POST entirely, or a trailing-slash redirect that turned a POST into a GET on the way (301 and 302 allow that, 307 does not). If the Allow header is missing, the server is technically violating RFC 9110, and the framework route table is the next place to look.