HTTP Status Codes

Quick lookup with explanations

{{ cat.range }} — {{ cat.name }}
{{ c.code }} {{ c.name }}
{{ c.desc }}

What are HTTP status codes?

HTTP status codes are three-digit numbers returned by the server in every response to a client. They were first introduced in 1991 with HTTP/0.9, standardized in RFC 2616 (1999), and later consolidated in RFC 7231 (2014) and the current RFC 9110 (2022). The status code is part of the Status-Line of an HTTP response (e.g. HTTP/1.1 200 OK) and tells the client whether the request succeeded, was redirected, or failed. Without these codes, a browser would not know whether to render an HTML page, follow a redirect, or display an error.

The 5 status code classes

The first digit of the status code identifies the class; the second and third digits identify the specific outcome:

  • 1xx Informational — Interim responses. The server has received the request and is still processing it (e.g. 100 Continue, 103 Early Hints).
  • 2xx Success — The request was successfully received, understood, and accepted (200 OK, 201 Created, 204 No Content).
  • 3xx Redirection — Further action is needed to complete the request — usually a redirect (301, 302, 304, 307, 308).
  • 4xx Client Error — The request contains an error or asks for something not allowed (400, 401, 403, 404, 429).
  • 5xx Server Error — The server failed to fulfill a valid request (500, 502, 503, 504).

The most important status codes in detail

These codes show up daily in developer work — in browser DevTools network tab, API responses, and server logs:

  • 200 OK — Standard success response. Body contains the requested representation.
  • 301 Moved Permanently — Permanent redirect. Search engines transfer PageRank to the new URL. Aggressively cached by browsers.
  • 302 Found — Temporary redirect. Search engines keep the old URL in the index.
  • 304 Not Modified — Cache validation. Response to If-None-Match/If-Modified-Since; body is empty, browser uses its cache.
  • 400 Bad Request — Request is syntactically invalid (e.g. malformed JSON, invalid query parameters).
  • 401 Unauthorized — Authentication is missing or invalid. A WWW-Authenticate header should follow.
  • 403 Forbidden — Authentication is fine, but the user has no permission for the resource.
  • 404 Not Found — Resource does not exist. The most common error on the web.
  • 410 Gone — Resource was here but is permanently removed. Stronger than 404 — actively says "never coming back".
  • 418 I'm a teapot — April Fools' joke from RFC 2324 (1998). Actually used by Cloudflare and some servers as a humorous response.
  • 429 Too Many Requests — Rate limit reached. The Retry-After header indicates the wait time.
  • 500 Internal Server Error — Generic server error, often an unhandled exception. Check the logs!
  • 502 Bad Gateway — A reverse proxy (Nginx, Cloudflare) received an invalid response from the upstream server.
  • 503 Service Unavailable — Server overloaded or in maintenance. Temporary.
  • 504 Gateway Timeout — A reverse proxy didn't receive a timely response from the upstream.

301 vs. 302 — what's the difference?

Both codes redirect the browser to another URL; the difference lies in permanence and therefore SEO behavior. A 301 signals "this URL no longer exists — use the new address permanently". Google transfers full PageRank to the destination, browsers cache the redirect aggressively (often for months). A 302 means "just redirect this one request, the old URL remains the master". Search engines keep the original in the index and re-check every request. Rule of thumb: for domain migrations, URL restructuring, or permanent slug changes, ALWAYS use 301. For A/B tests, maintenance redirects, or the "after successful POST go to summary" flow, 302 (or better 303 / 307) is correct. A common mistake: using 302 instead of 301 during site migrations — Google then loses the link equity.

Cache-relevant status codes

HTTP caching is one of the most important performance optimizations on the web — and status codes play a central role. The most important caching code is 304 Not Modified: the browser sends an If-None-Match header with the last stored ETag, the server checks whether the resource has changed, and replies with 304 and an empty body if not — the browser uses its local copy. This saves bandwidth dramatically. 200 OK with Cache-Control: max-age=31536000, immutable won't be re-requested for a year. 301 is often cached by browsers for months — be careful when switching from 301 back to 302! 503 with Retry-After tells CDNs when a retry makes sense. Header combinations like Cache-Control, ETag, Last-Modified and Vary determine actual cache behavior.

Common use cases

REST APIs use status codes systematically: 200 for read resources, 201 after successful POST with a Location header, 204 for DELETE without body, 400 for validation errors, 401 for missing auth token, 404 for nonexistent resources, 422 when the data is valid JSON but violates business rules. Browser debugging: the DevTools network tab's status column immediately shows where problems are — red 4xx/5xx rows are the first thing to check. Server configuration (Nginx, Apache, Caddy) uses status codes for rewrite rules, error pages, and health checks. SEO migrations live or die by correct 301 redirects: map old URLs to new ones, update sitemap.xml, use Search Console Change-of-Address. Monitoring tools like Uptime Robot poll for 2xx and alert on 5xx.

Frequently asked questions

Which status code for "page has moved"?

For a permanent move — new URL structure, domain change, deleted post with a new equivalent — use 301 Moved Permanently. For short-lived redirects (maintenance, A/B test, temporary campaign) use 302 Found or, if the HTTP method must be strictly preserved, 307 Temporary Redirect. For SEO the choice is critical: 301 transfers PageRank, 302 does not.

What does 200 OK with no body mean — and when should I use 204?

200 OK with an empty body is technically allowed — but clients usually expect content. 204 No Content is the clean choice when the server intentionally sends no body (e.g. after a successful PUT or DELETE). The key difference: 204 must not have a body per RFC 9110, while 200 is expected to have one. APIs should consistently use 204 for no-content responses — it saves bandwidth and makes the semantics clear.

When should I use 403 instead of 401?

The rule of thumb is simple: 401 Unauthorized means "you're not logged in — please authenticate". 403 Forbidden means "you're logged in, but you don't have permission for this action". Example: an unauthenticated user calls /admin → 401. A logged-in regular user calls /admin → 403. On 401, the server should return a WWW-Authenticate header indicating the expected auth scheme (e.g. Bearer, Basic).

Are there non-standard status codes?

Yes — providers and frameworks have established their own codes. Famous: 418 I'm a teapot from RFC 2324 (April Fools' but actually implemented). Cloudflare uses 520 (Web Server Returned an Unknown Error), 521 (Web Server is Down), 522 (Connection Timed Out), 523 (Origin Is Unreachable), 524 (A Timeout Occurred), 525/526 (SSL issues). Microsoft IIS has 440 (Login Timeout), AWS has 460/463/464. Nginx uses 444 (No Response — closes the connection without responding). These aren't in the RFC but are common in practice and usually classified in the 4xx/5xx ranges.

How HTTP status codes really work

An HTTP status code is part of the status line returned by the server in every response: HTTP/1.1 200 OK. The three-digit number is machine-readable; the reason phrase ("OK", "Not Found") is for humans only and has been explicitly deprecated since RFC 7230 — clients must not parse it. HTTP/2 and HTTP/3 drop the reason phrase entirely, leaving only the number in the :status pseudo-header. Practical consequence: if your proxy or Lua script depends on the reason phrase, you are breaking the HTTP spec.

The first digit classifies the response and is intentionally coarse: 1xx means "hold on, I'm thinking" (Informational), 2xx "everything's fine" (Success), 3xx "look elsewhere" (Redirection), 4xx "your fault" (Client Error), 5xx "my fault" (Server Error). This classification lets clients handle unknown codes sensibly: a 299 is treated like 200, a 599 like 500. That mechanism allows IANA-registered extensions like 451 (Unavailable For Legal Reasons, RFC 7725) to be added without a client update. The official IANA registry lists all accepted codes; custom 6xx or x99 codes violate the spec and break strict reverse proxies like nginx.

Idempotency and cacheability are tied directly to the status code. By default browsers and proxies only cache responses with 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501 — a 200 without explicit Cache-Control can be heuristically stored for hours. With 301 (Moved Permanently) the browser often caches the redirect permanently, which creates nasty behaviour if you want to undo the redirect later — use 302 or 307 if you are not 100% certain. RFC 7231 specifies which methods stay idempotent under which codes: a 405 on POST tells the client the method is generally forbidden, a 409 on PUT tells them the concrete resource state conflicts.

Code classes in detail

Each of the five classes has semantic guarantees you should respect when designing APIs.

  • 1xx Informational: interim reply, request not yet complete. 100 Continue for Expect: 100-continue, 101 for WebSocket upgrade, 103 Early Hints allows preload hints before the final response.
  • 2xx Success: request accepted and processed. 200 default, 201 after POST with a new resource URL in the Location header, 204 without body (e.g. DELETE), 206 only for range requests.
  • 3xx Redirection: client must retry elsewhere. 301/308 permanent, 302/307 temporary, 303 for POST-redirect-GET (PRG pattern), 304 only as response to If-Modified-Since/If-None-Match.
  • 4xx Client Error: request is syntactically or semantically wrong, retry without changes is pointless. 400 generic error, 401 = not authenticated (login missing), 403 = authenticated but not authorized, 422 for syntactically parsed but semantically invalid data.
  • 5xx Server Error: server failed, retry with backoff is sensible. 500 catch-all, 502 upstream replied with garbage, 503 server knows its own limits (maintenance, overload), 504 upstream did not reply in time. Clients should honour Retry-After headers on 503.

Concrete examples in REST APIs

How to map real-world API situations to the right code — this mirrors what GitHub, Stripe and AWS do in production.

  • POST /orders succeeded → 201 Created with Location: /orders/42. Stripe uses this for every resource creation.
  • GET /users/me without a bearer token → 401 Unauthorized with WWW-Authenticate: Bearer. With a valid token but no admin role → 403 Forbidden.
  • PATCH /articles/5 with stale If-Match ETag → 412 Precondition Failed. Solves the classic "lost update" problem without locking.
  • DELETE on an already deleted resource → 204 No Content (idempotent) or 410 Gone if the deletion is part of the public API contract and won't come back.
  • POST exceeding a 60 req/min limit → 429 Too Many Requests with Retry-After: 30 and X-RateLimit-Remaining: 0. GitHub and Twitter do exactly this.

Common mistakes and edge cases

Many APIs confuse 401 with 403: 401 means "I don't know who you are" (no login or expired token), 403 means "I know who you are but you still can't". Returning 403 for both blocks legitimate token-refresh logic in the client. Another classic: a 200 with {"error": "..."} in the JSON body. This breaks monitoring tools like Datadog APM or Sentry, which filter by HTTP status — broken requests then look like successes. Equally tricky: 307 and 308 preserve the HTTP method on redirect (POST stays POST), 301 and 302 historically allowed rewriting to GET — many clients still do, causing surprising data loss. For login endpoints reached via POST, always use 303 See Other to make the PRG pattern explicit. Last trap: Cloudflare returns its own 52x codes (520-527) that are not IANA-registered and only indicate Cloudflare-specific edge problems — never reuse them in your own API.

Frequently asked questions about HTTP status codes

What is the difference between 401 and 403?
401 Unauthorized means the server could not authenticate the user — either the Authorization header is missing or the token is invalid. 403 Forbidden means the user is known but does not have permission for the action. On 401 the client should trigger login or token refresh, on 403 that makes no sense.
When should I return 422 instead of 400?
400 Bad Request is for requests that fail at the syntactic level — broken JSON, missing headers, wrong Content-Type. 422 Unprocessable Entity (WebDAV, RFC 4918, but de-facto standard for REST) is for syntactically correct bodies whose values violate validation rules — email without @, negative prices, missing required fields. Rails and Laravel use 422 as the default for validation errors.
Which code is correct for an expired token?
401 Unauthorized with a specific WWW-Authenticate header: WWW-Authenticate: Bearer error="invalid_token", error_description="The access token expired" per RFC 6750. The client then knows a refresh is the right next step instead of forcing a full re-login.
Should I return 200 with error JSON or 4xx with error JSON?
Always use the matching 4xx/5xx status code. APM tools, load-balancer health checks, browser devtools and HTTP client retry logic all build on the status code. A 200 with error JSON is an anti-pattern that masks real issues. RFC 7807 (Problem Details for HTTP APIs) defines an established JSON format with type, title, status, detail.
What does 418 I'm a teapot actually mean?
418 comes from the April-fools RFC 2324 (Hyper Text Coffee Pot Control Protocol) and is officially reserved: a server told to brew coffee while it is in fact a teapot should return 418. In practice, some APIs (e.g. Cloudflare's mock for scraping bots) use it as an Easter egg. Do not use it in real production APIs.
Which redirect code is best for SEO?
301 Moved Permanently passes PageRank to the new URL and is the standard for permanent domain or path moves. 302 Found keeps PageRank on the old URL and is meant for temporary redirects (e.g. A/B tests or geo-targeted detours). Google treats 308 (permanent, method-preserving) and 307 (temporary, method-preserving) the same as 301/302 for SEO purposes since 2018.

Related tools