HTTP Status Codes Explained: The Ones That Actually Bite in Production
A practical guide to HTTP status codes — what 2xx, 3xx, 4xx, and 5xx mean, the codes engineers confuse most (401 vs 403, 502 vs 504), and how to debug them fast.
HTTP Status Codes Explained: The Ones That Actually Bite in Production
Every web request comes back stamped with a three-digit number, and most of the time you never look at it — 200, move on. Then a deploy goes sideways at 2 a.m., the dashboard fills with red, and suddenly that number is the only clue you have. The problem is that the textbook definition ("401 means Unauthorized") almost never tells you whose fault it is or what to check first. This guide walks through how HTTP status codes are organized, the handful that engineers confuse most often, and how to read them as a debugging signal instead of a riddle.
If you want a searchable reference while you read, the HTTP Status Code Explorer covers all 70+ codes — every entry pairs the standard name with the real production causes and a short troubleshooting checklist.
The Four Classes: Reading the First Digit
The first digit of a status code is the fastest triage tool you have. RFC 9110, the current HTTP semantics specification published in June 2022, defines five classes, and four of them show up constantly:
- 2xx — Success. The request worked.
200 OKis the default,201 Createdconfirms a resource was made, and204 No Contentsays "done, nothing to return" (common after a DELETE). - 3xx — Redirection. The resource lives somewhere else, or hasn't changed.
301,302,304,307, and308all live here. - 4xx — Client error. You sent something the server couldn't or wouldn't accept.
400,401,403,404,413, and429are the usual suspects. - 5xx — Server error. The server failed to fulfill a valid request.
500,502,503, and504mean the problem is on the server side, not yours.
The mental shortcut: 4xx is "you have a problem," 5xx is "the server has a problem." When a frontend dev pings me about an error, my first question is always "what's the first digit?" — because a 4xx and a 5xx send the investigation in completely opposite directions.
The Pairs Everyone Confuses: 401 vs 403, 502 vs 504
Two pairs of codes cause more wasted hours than the rest of the table combined.
401 vs 403. A 401 Unauthorized means "we don't know who you are" — you sent no credentials, an expired token, or a bad API key. The fix is to log in or refresh the token. A 403 Forbidden means "we know exactly who you are, and you're not allowed in" — you authenticated fine, but this resource isn't yours to touch. The names are both vague and unhelpful, but the consequence is huge: SDKs typically respond to a 401 by silently refreshing the token and retrying. If you return 401 for a permission problem, the client loops on token refresh forever instead of surfacing the real "you don't have access" message.
502 vs 504. Both look like "the upstream is broken," but they're different failure modes. A 502 Bad Gateway means your reverse proxy connected to the upstream and got back garbage — an empty response, malformed headers, or a connection reset mid-flight. The upstream crashed or isn't listening. A 504 Gateway Timeout means the proxy connected fine but waited too long and gave up. The upstream is slow — a long query, a deadlock, a lagging dependency. The trap is bumping proxy_read_timeout to fix a 502: timeouts only matter for the slow case. Raising a timeout never fixes a crashed backend; it just makes you wait longer to see the same error.
A Real Debugging Example: The Post-Deploy 502 Storm
Here's a scenario I've lived through more than once. You roll out a new build and the error rate jumps to 12%, all 502 Bad Gateway. Panic instinct says "bump the timeout" — wrong instinct.
Pull up 502 in the Explorer and the meaning reads: upstream crashed, not listening, or returned an invalid response. The troubleshooting checklist is concrete, not "contact your administrator":
curlthe backend port directly — if you getconnection refused, the process isn't listening.tailthe nginx error log — aconnect() failedline confirms the proxy can't reach the upstream.- Check that the new container bound to
0.0.0.0, not127.0.0.1— a container that binds to loopback is invisible to nginx on the host network.
Nine times out of ten, step 3 is the culprit: the deploy changed a bind address and the backend is up but unreachable. Root cause in three minutes, instead of an hour of staring at a 502 that a timeout change can't possibly fix. When I'm wiring up these checks I keep the cURL cheat sheet open for the exact flags to hit an upstream directly.
Redirects and Rate Limits: 301/302/308 and 429
Redirects deserve their own decision matrix, because a wrong choice here caches for months. Permanent move where browsers and SEO should bake it in: 301. Permanent move that must preserve a POST method and body: 308. Temporary move, method downgrade is fine: 302. Temporary move that must preserve the method: 307. The classic mistake is using a 302 for an HTTPS migration — HSTS, SEO, and browser caches won't commit to a temporary redirect, so the move never sticks. The safe pattern: ship 302 first, watch for a week, then switch to 301 once the new URL is stable, because a wrong 301 is brutally hard to claw back out of caches.
429 Too Many Requests is the other one worth a note. It means the server is rate-limiting you, and the response should carry a Retry-After header telling you how many seconds to wait. The right client behavior is exponential backoff with jitter — 1s, 2s, 4s, 8s, randomized by ±50% — so a herd of clients doesn't all retry at the same instant and hammer the server back into a wall.
For the infrastructure side of these — client_max_body_size for that 413 upload error, proxy_read_timeout for WebSocket disconnects, upgrade-header forwarding — the nginx cheat sheet has the config snippets ready to copy.
Make the Number Work for You
Status codes aren't trivia to memorize — they're a triage signal you read in seconds. First digit tells you who owns the problem. The specific code, paired with the real cause and a checklist, tells you what to check first. The difference between a five-minute fix and an hour of flailing is usually just knowing that a 502 is a broken upstream and not a slow one. Keep a good reference one tab away, read the first digit before you panic, and let the number do the triage for you.
Made by Toolora · Updated 2026-06-13