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?+
What is the difference between 401 and 403?+
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?+
Why am I seeing 502 instead of 504?+
Do I need to handle status codes my client does not recognise?+
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.Read next
- Try the tool: HTTP status code reference
- Try the tool: HTTP header inspector
- Try the tool: curl command builder
Tags: #http, #web-dev, #api-design, #debugging