Skip to content
Control Plane Labs

Every HTTP status code, and when it shows up

A working reference to the HTTP status codes you will actually meet in production, what each one really means, and the pairs everyone confuses.

Ben Ennis

Published July 27, 2026

There are around sixty registered HTTP status codes. You will meet maybe twenty in a career, and confuse about six of them repeatedly. This is a reference for the ones that show up, organised by what actually emits them, with the authoritative definitions in RFC 9110 §15 and the complete list in the IANA HTTP Status Code Registry.

The class rule is the most useful thing in the specification and the least known. A client that receives 499 or 599 must treat it as 400 or 500 respectively. It does not have to understand the code, only the digit. That is what makes the registry extensible.

1xx: mostly invisible

100 Continue exists so a client can send Expect: 100-continue and find out whether a large request body is going to be rejected before uploading it. curl does this automatically for bodies over 1 KB, which is why you occasionally see a mysterious one-second stall — the server never answered and curl waited out its timeout.

101 Switching Protocols is the WebSocket upgrade handshake, and essentially nothing else.

103 Early Hints, defined in RFC 8297, lets a server send Link preload headers before the real response is ready. It is genuinely useful behind a slow origin, and genuinely dangerous with a cache that does not understand it.

2xx: not all success is the same

200 OK needs no explanation. The other four do.

201 Created should carry a Location header pointing at the thing you just made. An API that returns 201 with no Location is making the client guess.

202 Accepted means “queued, not done”. This is the correct answer for an async job submission and the incorrect answer for anything the client will assume is complete.

204 No Content means success with no body, and the response must not have one. Sending a JSON body with a 204 is a spec violation that some HTTP/2 stacks will reject outright rather than ignore. If you have something to say, use 200.

206 Partial Content is the range request response — video seeking, resumable downloads. If you serve large files and never see a 206, your Accept-Ranges handling is broken.

3xx: the four-way confusion

This is where most real-world mistakes live, and it comes down to two independent questions: is the redirect permanent, and may the client change the method?

Code Permanent Method preserved
301 yes no (POST becomes GET)
302 no no (POST becomes GET)
307 no yes
308 yes yes

301 and 302 are the historical pair, and both were widely implemented to rewrite POST into GET regardless of what the specification originally said. 307 and 308 were introduced to give unambiguous method-preserving semantics — RFC 7538 defines 308 and is explicit that the request method must not change.

The practical rule: for an HTTP-to-HTTPS redirect on a site that accepts POST, use 308. Using 301 there silently converts form submissions to GETs and drops the body, which presents as “the form works on the second try” and takes a day to find.

304 Not Modified is a conditional-request response, not a redirect. It carries no body, and the client reuses its cached copy. Cache semantics are in RFC 9111. A CDN reporting a high 304 rate is working correctly.

4xx: the long tail

400 Bad Request is the catch-all for malformed syntax. Reserve it for genuinely unparseable requests, not for validation failures.

401 Unauthorized vs 403 Forbidden is the most-argued pair in API design, and the specification is clearer than the argument suggests. 401 means the request lacked valid authentication credentials, and it must include a WWW-Authenticate header telling the client how to authenticate. 403 means the server understood who you are and is refusing anyway. Authentication missing, versus authorisation denied. If you send a 401 with no WWW-Authenticate header, you meant 403.

404 Not Found vs 410 Gone: 404 means “no representation here, might be back”, 410 means “deliberately removed, stop asking”. Search engines de-index a 410 faster.

405 Method Not Allowed must include an Allow header listing what is permitted. Almost nobody does this.

409 Conflict is for state conflicts — a duplicate unique key, a concurrent edit. 412 Precondition Failed is its optimistic-locking sibling, returned when an If-Match or If-Unmodified-Since header does not hold.

413 Content Too Large and 415 Unsupported Media Type are usually emitted by the proxy, not your application. An unexplained 413 on a file upload is nginx’s client_max_body_size about 90% of the time.

418 is registered as “(Unused)” in the IANA registry, reserved so nobody assigns it. It originated in RFC 2324, the April Fools’ coffee-pot protocol, and survives because too many implementations shipped it.

421 Misdirected Request shows up with HTTP/2 connection coalescing: a client reused a connection for a hostname this server does not serve. Rare, confusing, and almost always a TLS certificate covering more names than the vhost config does.

422 Unprocessable Content is syntactically valid but semantically wrong — the right code for validation failures. It came from WebDAV (RFC 4918) and was promoted into core HTTP by RFC 9110.

425 Too Early (RFC 8470) rejects a request sent in TLS 1.3 early data, because 0-RTT data is replayable.

428, 429, and 431 come from RFC 6585. 429 Too Many Requests should carry Retry-After; a rate limiter that does not send one is forcing every client to guess, and they will guess badly.

451 Unavailable For Legal Reasons (RFC 7725) is for legal blocking specifically, not for geo-licensing.

5xx: whose fault is it

500 Internal Server Error is an unhandled exception. 501 Not Implemented means the method is unrecognised entirely.

The pair worth internalising is 502 versus 504. 502 Bad Gateway means the proxy reached the upstream and got something it could not parse — or could not connect at all. 504 Gateway Timeout means the proxy connected fine and the upstream never finished in time. 502 points at a crashed or misconfigured backend; 504 points at a slow one. They lead to completely different investigations.

503 Service Unavailable is the honest “temporarily down” and should carry Retry-After. It is also what a well-configured load balancer returns when no backends are healthy, which makes it the correct thing to page on.

511 Network Authentication Required is the captive-portal code from RFC 6585. If you see it in production, someone’s traffic is going through a hotel wifi.

Codes that are not really HTTP

499 is nginx-specific and means the client closed the connection before the server responded. It is not in the IANA registry — the 452–499 range is entirely unassigned — so no client will ever see it. It only appears in your access logs, and a spike in 499s means users or upstream timeouts are giving up on you, not that anything returned an error. Cloudflare similarly uses codes in the 520–530 range for its own failure modes.

When a code is genuinely unfamiliar, MDN’s status reference is the fastest lookup, and the HTTP status code reference here gives the same information with the common causes attached.

Frequently asked questions

Should a validation error be 400 or 422?+
422 Unprocessable Content. Use 400 when the request is malformed at the syntax level — broken JSON, bad framing — and 422 when it parses correctly but fails your business rules. Both are 4xx, so any client that does not distinguish them still behaves sensibly.
What is the difference between 401 and 403?+
401 means no valid credentials were supplied and the response must include a WWW-Authenticate header explaining how to supply them. 403 means the credentials were understood and access is still denied. If retrying with correct credentials could succeed, it is a 401.
Which redirect should I use for HTTP to HTTPS?+
308 if the site accepts POST, 301 if it is read-only. 301 and 302 permit clients to rewrite POST into GET, which silently drops request bodies. 308 preserves the method and the body, per RFC 7538.
Why am I seeing 502 instead of 504?+
502 means the proxy could not get a usable response — the upstream refused the connection, died mid-response, or spoke something that was not valid HTTP. 504 means the upstream was reachable but too slow. Check whether your backend process is running before you start raising timeouts.
Do I need to handle status codes my client does not recognise?+
Only by class. RFC 9110 requires a client to treat an unrecognised status as the generic x00 of its class, so an unknown 4xx behaves as 400 and an unknown 5xx as 500. That is what lets vendors add codes like Cloudflare's 52x without breaking anything.

Tags: #http, #web-dev, #api-design, #debugging