Cheatsheet
Every HTTP status code and what it actually means
Grouped by class, with the name from the spec and the situation that actually produces it. The last two sections cover the codes that are not in any RFC: the ones nginx and Cloudflare invent, which is where a lot of confusing production traffic comes from.
The five classes at a glance
| Range | Meaning | Whose fault |
|---|---|---|
| 1xx | Informational. The request was received and processing continues. | Nobody. Rarely surfaced to application code. |
| 2xx | Success. The request was received, understood, and accepted. | Nobody. |
| 3xx | Redirection. Further action is needed to complete the request. | Nobody, unless it loops. |
| 4xx | Client error. The request was malformed, unauthenticated, or asked for something absent. | The caller, in theory. Often the API contract in practice. |
| 5xx | Server error. The request was valid but the server failed to fulfill it. | You. Page someone. |
Gotcha: only 4xx and 5xx are errors. A client library that treats every non 200 response as a failure will break on 201, 204, and 304, all of which are perfectly successful.
1xx informational and 2xx success
| Code | Name | When you see it |
|---|---|---|
| 100 | Continue | The client sent Expect: 100-continue before a big body and the server is telling it to proceed. |
| 101 | Switching Protocols | A WebSocket handshake succeeding. The connection stops being HTTP after this. |
| 102 | Processing | A long running WebDAV request. Deprecated and effectively dead. |
| 103 | Early Hints | A CDN sending preload Link headers before the real response. Cuts real page load time. |
| 200 | OK | The default success. A body is expected on GET. |
| 201 | Created | A POST created a resource. Include a Location header pointing at it. |
| 202 | Accepted | The work was queued, not done. The right answer for an async job endpoint. |
| 203 | Non-Authoritative Information | A proxy modified the origin's response. Rare and usually a surprise. |
| 204 | No Content | Success with nothing to return. The correct answer to a DELETE or a silent PUT. |
| 205 | Reset Content | Tells the client to clear the form it just submitted. Almost never used. |
| 206 | Partial Content | A Range request succeeded: video scrubbing, resumable downloads. |
| 207 | Multi-Status | WebDAV: one response body carrying per resource statuses. |
| 208 | Already Reported | WebDAV: this binding was already enumerated earlier in the same response. |
| 226 | IM Used | Delta encoding was applied. You will go a career without meeting it. |
Gotcha: a 204 must carry no body at all. Sending JSON with it makes some HTTP clients hang waiting for content length that never arrives. If you have something to say, use 200.
3xx redirection
| Code | Name | When you see it |
|---|---|---|
| 300 | Multiple Choices | Several representations exist and the client must pick. Vanishingly rare. |
| 301 | Moved Permanently | A permanent URL change. Browsers cache it aggressively and search engines pass ranking through it. |
| 302 | Found | A temporary redirect. Most clients switch POST to GET on it, which the spec never intended. |
| 303 | See Other | Explicitly says to follow with GET. The correct post and redirect pattern after a form. |
| 304 | Not Modified | A conditional request matched an ETag or Last-Modified. Success, with no body. |
| 305 | Use Proxy | Deprecated for security reasons. Modern clients ignore it. |
| 307 | Temporary Redirect | Like 302 but the method and body must be preserved. Use it instead of 302 for APIs. |
| 308 | Permanent Redirect | Like 301 but the method is preserved. The right code for a permanent API move. |
Gotcha: a 301 is close to irreversible in the field. Browsers cache it indefinitely, so a mistaken permanent redirect keeps firing for users who never see your fix. Ship 302 or 307 first, confirm the target, then promote it.
4xx client errors
| Code | Name | When you see it |
|---|---|---|
| 400 | Bad Request | Malformed syntax: broken JSON, a bad header, an impossible query string. |
| 401 | Unauthorized | Not authenticated. Misnamed: it means unauthenticated. Must include WWW-Authenticate. |
| 402 | Payment Required | Reserved for decades, now used by some APIs for a lapsed subscription. |
| 403 | Forbidden | Authenticated but not allowed. Repeating the request will not help. |
| 404 | Not Found | No resource at that URL. Also the polite way to hide something from a user who may not know it exists. |
| 405 | Method Not Allowed | The route exists but not for that verb. Must return an Allow header listing the verbs that work. |
| 406 | Not Acceptable | No representation matches the Accept header the client sent. |
| 407 | Proxy Authentication Required | Like 401, but the proxy is the one demanding credentials. Common on corporate networks. |
| 408 | Request Timeout | The client opened a connection and did not finish sending in time. |
| 409 | Conflict | State conflict: a duplicate unique value, or an edit against a stale version. |
| 410 | Gone | Deliberately deleted and not coming back. Search engines drop the URL faster than on a 404. |
| 411 | Length Required | The server refuses a body without a Content-Length header. |
| 412 | Precondition Failed | An If-Match or If-Unmodified-Since guard did not hold. Optimistic locking working correctly. |
| 413 | Content Too Large | Upload over the limit. Usually the proxy, not your app. Formerly Payload Too Large. |
| 414 | URI Too Long | A GET with a giant query string. Switch to POST with a body. |
| 415 | Unsupported Media Type | Wrong Content-Type on the request. The classic missing application/json header. |
| 416 | Range Not Satisfiable | The requested byte range lies outside the file. |
| 417 | Expectation Failed | The server will not honor the Expect header the client sent. |
| 418 | I'm a teapot | An April Fools joke from 1998, permanently reserved. Handy as an unmistakable test value. |
| 421 | Misdirected Request | The connection reached a server that cannot serve that authority. An HTTP/2 coalescing artifact. |
| 422 | Unprocessable Content | Syntactically valid but semantically wrong. The standard validation failure code. |
| 423 | Locked | WebDAV: the resource is locked by someone else. |
| 424 | Failed Dependency | WebDAV: a previous request in the sequence failed, so this one cannot run. |
| 425 | Too Early | Refusing a replayable early data request during a TLS 1.3 zero round trip handshake. |
| 426 | Upgrade Required | The server insists on a different protocol, listed in the Upgrade header. |
| 428 | Precondition Required | The server demands a conditional request, to stop blind overwrites. |
| 429 | Too Many Requests | Rate limited. Always send Retry-After; clients cannot back off intelligently without it. |
| 431 | Request Header Fields Too Large | Usually an oversized cookie jar rather than a genuinely huge header. |
| 451 | Unavailable For Legal Reasons | Censorship or a takedown. The number is a Fahrenheit 451 reference. |
Gotcha: 401 versus 403 trips up nearly every API. 401 means "I do not know who you are, try authenticating"; 403 means "I know exactly who you are and the answer is still no". Returning 401 for a permissions failure sends clients into a pointless token refresh loop.
5xx server errors
| Code | Name | When you see it |
|---|---|---|
| 500 | Internal Server Error | An unhandled exception. Generic by design, so the detail belongs in your logs. |
| 501 | Not Implemented | The server does not recognize the method at all. Not the same as 405. |
| 502 | Bad Gateway | A proxy got garbage or nothing from upstream. Usually your app crashed or never bound its port. |
| 503 | Service Unavailable | Overloaded or in maintenance. Send Retry-After. This is the correct maintenance page code. |
| 504 | Gateway Timeout | Upstream accepted the connection but never answered in time. The slow query smell. |
| 505 | HTTP Version Not Supported | The protocol version in the request line is refused. |
| 506 | Variant Also Negotiates | A content negotiation misconfiguration that loops on itself. |
| 507 | Insufficient Storage | WebDAV: out of disk. Occasionally repurposed by storage APIs. |
| 508 | Loop Detected | WebDAV: an infinite loop while processing the request. |
| 510 | Not Extended | An extension the server requires was missing from the request. |
| 511 | Network Authentication Required | A captive portal. Hotel and airport wifi, not your server. |
Gotcha: 502 and 504 both come from the proxy, never from your application code. 502 means upstream answered with something invalid or died mid response; 504 means it never answered at all. Check the process on a 502 and the query or the timeout on a 504.
Non standard codes from proxies and CDNs
These are not in any RFC. They appear in access logs and dashboards, so knowing them saves an hour of searching the spec for something that is not there.
| Code | Source | Meaning |
|---|---|---|
| 444 | nginx | Connection closed with no response at all. Usually a deliberate abuse rule. |
| 494 | nginx | Request headers exceeded the buffer. The nginx flavor of 431. |
| 497 | nginx | A plain HTTP request arrived on an HTTPS port. |
| 499 | nginx | The client gave up and disconnected first. A spike means your responses are too slow. |
| 520 | Cloudflare | Origin returned something Cloudflare could not parse. The catch all. |
| 521 | Cloudflare | Origin refused the connection. Often a firewall blocking Cloudflare's IP ranges. |
| 522 | Cloudflare | TCP handshake to the origin timed out. |
| 523 | Cloudflare | Origin unreachable. Normally a DNS record pointing at nothing. |
| 524 | Cloudflare | Origin connected but did not finish responding inside the timeout window. |
| 525 | Cloudflare | TLS handshake with the origin failed, usually a cipher or protocol mismatch. |
| 526 | Cloudflare | The origin certificate is invalid or expired while strict SSL is on. |
| 530 | Cloudflare | Always paired with a 1xxx error in the body. Read that number, not the 530. |
Choosing the right code in your own API
| Situation | Return |
|---|---|
| POST created a record | 201 with a Location header |
| POST queued a background job | 202 with a status URL |
| DELETE succeeded, nothing to say | 204 |
| Body parsed but a field is invalid | 422 with per field errors |
| Body would not parse at all | 400 |
| Token missing or expired | 401 with WWW-Authenticate |
| Valid user, wrong role | 403 |
| Valid user, other tenant's record | 404, to avoid leaking existence |
| Email already registered | 409 |
| Client is over its quota | 429 with Retry-After |
| Planned maintenance window | 503 with Retry-After |
| Any error body at all | application/problem+json per RFC 9457 |
Gotcha: never return 200 with an error object in the body. Monitoring, retries, circuit breakers, and CDN caching all key off the status line, and a 200 tells every one of them that everything is fine.
Keep going
Status codes are one design decision inside a bigger one: REST vs GraphQL covers where they stop being the error channel at all, and the API stack guide covers the layers around them.
For the proxy side of a 502, see the Docker cheatsheet on container health checks, or browse the monitoring tools that alert on these codes. The cheatsheet index has everything else.