HTTP Status Codes Explained: What Each Class Means and When to Send It

How the five status classes work, the codes APIs get wrong (401 vs 403, 400 vs 422, 301 vs 308), what RFC 9110 changed, caching defaults, and the nginx and Cloudflare codes that show up in logs.

ยท 10 min read

Illustration for the HTTP Status Codes guide

Every HTTP response starts with a three-digit number, and that number is read by far more software than the body is. Browsers decide whether to follow a redirect or show an error page from it, caches decide whether to store the response, retry libraries decide whether to try again, search engines decide whether to keep a URL in the index, and monitoring decides whether to wake someone up. Getting it right is cheap; getting it wrong produces bugs that look like they are somewhere else. This article explains how the codes are organised, which ones matter in practice and where people go wrong. The HTTP Status Codes reference lists every registered code with its RFC section if you need to look one up while you read.

Where status codes are defined

The core codes live in RFC 9110, HTTP Semantics, published in June 2022. It replaced RFC 7231 (2014), which in turn replaced RFC 2616 (1999), and it brought every general-purpose code into one place in its section 15. Codes defined for narrower purposes stay in their own documents: 429 Too Many Requests, 428 and 431 come from RFC 6585, 451 Unavailable For Legal Reasons from RFC 7725, 425 Too Early from RFC 8470, 103 Early Hints from RFC 8297, and the WebDAV codes 207, 423, 424 and 507 from RFC 4918. IANA keeps the HTTP Status Code Registry that points at all of them.

RFC 9110 also renamed two codes to match how they are used. 413 is now "Content Too Large" (it was "Payload Too Large" and before that "Request Entity Too Large"), and 422, which used to be WebDAV-only, became the general "Unprocessable Content". If you see the old names in a framework, they mean the same thing.

The five classes and the fallback rule

The first digit carries most of the meaning, and software relies on it.

ClassMeaningWhat clients do
1xx InformationalInterim response; a final one followsKeep waiting (100 Continue, 103 Early Hints)
2xx SuccessfulThe request workedUse the body, if there is one
3xx RedirectionMore action needed, usually follow LocationFollow automatically for 301, 302, 303, 307, 308
4xx Client errorThe request is wrong or not allowedDo not retry unchanged
5xx Server errorA valid request failed on the serverRetrying later may work

RFC 9110 section 15 adds an important rule: a client that receives a code it does not recognise must treat it as the x00 code of the same class. An unknown 299 is handled like 200, an unknown 460 like 400 and an unknown 599 like 500. That is why inventing a private status code buys you nothing. Proxies, browsers and libraries will only ever see the class, so put the detail in the body (RFC 9457 Problem Details is a good format for it) and keep the code standard.

Decision flow for choosing a status code: did the request succeed (2xx), is the resource elsewhere (3xx), is the client at fault (4xx: 400 malformed, 401 no credentials, 403 not allowed, 404 missing, 409 conflict, 422 invalid values, 429 too many), or did the server fail (5xx: 500 bug, 502 bad upstream, 503 overloaded, 504 upstream timeout)
Decision flow for choosing a status code: did the request succeed (2xx), is the resource elsewhere (3xx), is the client at fault (4xx: 400 malformed, 401 no credentials, 403 not allowed, 404 missing, 409 conflict, 422 invalid values, 429 too many), or did the server fail (5xx: 500 bug, 502 bad upstream, 503 overloaded, 504 upstream timeout)

The success codes worth knowing

Most APIs only need four 2xx codes. 200 OK is the default. 201 Created says a POST or PUT made something new and should carry a Location header pointing at it. 202 Accepted says the work was queued and has not finished, which is honest for asynchronous jobs: return a status URL the client can poll. 204 No Content says it worked and there is nothing to send back, common after DELETE.

Two details trip people up. A 204 response must not contain a body, and some proxies hang or corrupt the next response on the connection if you send one anyway. And returning 200 with {"error": "not found"} in the body is a bug, not a style choice: caches will store it, crawlers will index it and every client library will report success.

Redirects: permanence and method

The 3xx codes answer two independent questions: is the move permanent, and must the client repeat the same method and body?

CodePermanentKeeps POST as POSTTypical use
301 Moved PermanentlyYesNot guaranteedPages that moved for good
302 FoundNoNot guaranteedTemporary moves, login bounces
303 See OtherNoAlways changes to GETAfter a form POST (Post/Redirect/Get)
307 Temporary RedirectNoYesTemporary moves of API endpoints
308 Permanent RedirectYesYesPermanent moves of API endpoints

The "not guaranteed" column is history. Early browsers turned a POST into a GET when following 301 and 302, and so many did it that RFC 9110 now allows it. HTTP/1.1 introduced 307 and RFC 7538 introduced 308 so that a server can ask for the method to be kept. For ordinary page links the difference does not matter, which is why 301 remains the usual choice for site moves. For an endpoint that receives uploads or form posts, use 307 or 308, or the body will be lost.

304 Not Modified sits in this class but is not really a redirect. It answers a conditional request (If-None-Match with an ETag, or If-Modified-Since) and tells the client to use its cached copy. If you are building redirect rules, the Redirect Rules Generator writes them for Apache, Nginx, Netlify and Vercel.

The client errors APIs most often confuse

400 versus 422. 400 Bad Request is for requests the server cannot parse or that miss required parts: broken JSON, a missing parameter, a wrong type. 422 Unprocessable Content is for requests that parse fine but break a rule, such as an end date before the start date. Both are legitimate. Pick one convention for validation errors, document it and use it everywhere.

401 versus 403. The names are misleading. 401 Unauthorized means unauthenticated: there are no valid credentials, and a fresh login or token might work. It must include a WWW-Authenticate header naming the scheme. 403 Forbidden means the server knows who you are and the answer is still no. Sending 401 for a permissions problem makes clients loop through re-authentication for nothing.

403 versus 404. A server may answer 404 instead of 403 to avoid confirming that a private resource exists. GitHub does this for private repositories. It is a deliberate choice, not an error.

409 versus 412. 409 Conflict describes a clash with current state, such as a duplicate username. 412 Precondition Failed is narrower: the client sent If-Match with an ETag that is no longer current, so the server refuses to overwrite a newer version. Using ETags and 412 is the standard way to prevent lost updates.

429. Rate limiting has its own code from RFC 6585. Send Retry-After with a number of seconds or an HTTP date. Do not use 503 for one client exceeding its quota; 503 says the whole service is unavailable.

Server errors and what they point at

The 5xx codes are most useful when they tell an on-call engineer where to look. 500 means the application itself failed, usually an unhandled exception. 502 Bad Gateway means a proxy or load balancer got an invalid response from the upstream, typically because the application crashed or closed the connection. 503 Service Unavailable means overloaded or in maintenance, ideally with Retry-After; Google treats a short 503 as temporary and keeps the page indexed. 504 Gateway Timeout means the proxy gave up waiting; nginx's proxy_read_timeout defaults to 60 seconds and Cloudflare waits 100 seconds before returning its own 524.

Caching defaults

RFC 9110 section 15.1 lists the codes a cache may store with a heuristic lifetime when the response has no explicit freshness information: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501. Two consequences catch people out. A permanent redirect sent by mistake can stay in browser caches long after you fix the server. And a 404 returned briefly during a deploy can be cached by a CDN. Send explicit Cache-Control headers on error responses if you want them treated differently.

Codes you will see that are not in any RFC

Logs and dashboards contain codes no standard defines. They come from a specific product and describe what that product saw.

CodeSourceMeaning
444nginxnginx closed the connection without a response (return 444;)
499nginxThe client closed the connection before nginx answered
494 to 497nginxHeader too large, client certificate problems, HTTP sent to HTTPS port; clients see 400
520 to 526CloudflareOrigin problems: unknown error, refused, TCP timeout, unreachable, HTTP timeout, TLS failures
419LaravelCSRF token missing or expired

A wave of 499s means clients are giving up, often because the backend is slower than the client timeout. A 522 means Cloudflare could not even open a TCP connection to your origin, while 524 means it connected but the origin did not send an HTTP response in time. They point at different fixes: firewall and network for 522, slow requests for 524.

A short checklist

  • Return 4xx when the caller can fix the request and 5xx when they cannot. Retry logic depends on it.
  • Never wrap an error in a 200.
  • Use 201 with Location for creation, 204 for empty success, 202 for queued work.
  • Use 401 with WWW-Authenticate for missing credentials and 403 for missing permission.
  • Use 308 or 307 when an endpoint that accepts POST moves.
  • Send Retry-After with 429 and 503.
  • Put details in the body with application/problem+json rather than inventing codes.

When a header in the response is the real question, the MIME Type Lookup covers Content-Type, and the Security Headers Analyzer checks the rest.

Sources

  • RFC 9110, HTTP Semantics, section 15 (status codes) and 15.1 (cacheability), IETF, 2022.
  • IANA, Hypertext Transfer Protocol (HTTP) Status Code Registry.
  • RFC 6585, Additional HTTP Status Codes (428, 429, 431, 511).
  • RFC 7538, The Hypertext Transfer Protocol Status Code 308 (Permanent Redirect).
  • RFC 7725 (451), RFC 8470 (425), RFC 8297 (103), RFC 4918 (WebDAV codes), RFC 9457 (Problem Details).
  • nginx documentation for ngx_http_proxy_module and the return directive; Cloudflare documentation on 5xx errors.

Related searches

  • http status codes list
  • rfc 9110 status codes
  • 401 vs 403
  • 400 vs 422 validation error
  • 301 vs 308 redirect
  • cacheable status codes
  • nginx 499 meaning
  • cloudflare 522 error
  • 429 retry-after
  • unknown status code handling

Open the HTTP Status Codes