HTTP Status Codes Cheat Sheet

This HTTP status codes cheat sheet covers 28 sections: every 1xx to 5xx code with the headers it needs, which code to return in an API, 401 vs 403, 400 vs 422 and 301 vs 308, problem details error bodies, caching and 304, 429 and retries, redirects and SEO, curl and fetch, eleven frameworks, nginx and Apache, and how to fix 502, 503 and 504. Search it, filter it by level, and check a real response or redirect chain without leaving the page.

28sections
156snippets
64codes in the registry
4code tools

Updated October 14, 2026, checked against RFC 9110, the IANA status code registry, RFC 9457 and RFC 10008. Print it for a PDF copy.

/
level

404 Not Found

Nothing matches that search. Try a code such as 404, a word such as redirect or retry, or a header such as Location. Several words are combined, so every one of them has to match. Keys: / focuses the search, Esc clears it, t switches the theme.

01

Toolbox & Basics

look up any code, check a response, and read a status line

Status Code Toolbox: Lookup, Response Checker, Which Code

core
Ctrl+Enter checks the response. Check response reads a raw response or curl -i / curl -iL output, follows the redirect chain and lists missing or wrong headers. Look up codes takes numbers, ranges such as 4xx, or names such as "too many requests". Which code describes a situation in plain words, for example "user is logged in but not allowed". Should a client retry reads a status line plus headers and the request method.
output

          
Paste a response, a list of codes or a situation above, then pick an action.

Builders: Error Body, Redirect Rules, Status in Code, Error Pages

everyday
response

          

Anatomy of a Status Line

core

A response starts with the protocol, the code and a reason phrase

HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 1245
Cache-Control: max-age=60

<!doctype html>...

The first digit is the class

ClassMeaningWhat the client does
1xxinformational, an interim responsekeeps waiting for the final response
2xxsuccessuses the response
3xxredirectionfollows Location or uses its cache
4xxclient errorfixes the request before trying again
5xxserver errormay retry later, the request itself can be fine

An unknown code falls back to its class

HTTP/1.1 299 Something New

# a client that does not know 299 treats it as 200
# an unknown 4xx is handled like 400, an unknown 5xx like 500

See the Code From the Terminal

core

Print only the status code

curl -s -o /dev/null -w "%{http_code}\n" https://httpbingo.org/status/404

Show the status line and headers

curl -sI https://httpbingo.org/status/429 | head -3

Follow redirects and see every hop

curl -sIL "https://httpbingo.org/redirect-to?url=/status/204&status_code=301" | grep -iE "^(HTTP|location)"
02

The Five Classes

1xx informational, 2xx success, 3xx redirection, 4xx client errors, 5xx server errors at a glance

The Codes You Will See Most

core

Twenty codes cover nearly every real response

CodeNameIn one line
200OKthe request worked and the body is the result
201Createda new resource exists; Location points to it
204No Contentit worked and there is nothing to send back
301Moved Permanentlythe URL changed for good; update links
302Foundlook at another URL for now
304Not Modifiedyour cached copy is still good
307Temporary Redirectlike 302, but repeat the same method and body
308Permanent Redirectlike 301, but repeat the same method and body
400Bad Requestthe request is malformed
401Unauthorizedyou are not authenticated; log in or send a token
403Forbiddenyou are authenticated but not allowed
404Not Foundnothing at this URL
405Method Not Allowedthe URL exists, the method does not; see Allow
409Conflictthe request clashes with the current state
422Unprocessable Contentwell formed, but the data fails validation
429Too Many Requestsslow down; see Retry-After
500Internal Server Errorthe server crashed or failed unexpectedly
502Bad Gatewaya proxy got a bad answer from the upstream server
503Service Unavailableoverloaded or in maintenance; try later
504Gateway Timeouta proxy waited too long for the upstream server

Which Side Is at Fault

core

4xx blames the request, 5xx blames the server

SituationClassRetry the same request?
the body is invalid JSON4xx (400)no, fix it first
the token expired4xx (401)after refreshing the token
the database is down5xx (503)yes, with backoff
a bug threw an exception5xx (500)maybe, if the method is idempotent
too many requests4xx (429)yes, after Retry-After

Classes by number range

RangeRegistered codesNotes
100 to 199100, 101, 102, 103, 104104 is a temporary registration
200 to 299200 to 208, 226207, 208 and 226 come from WebDAV and delta encoding
300 to 399300 to 308305 is deprecated and 306 is unused
400 to 499400 to 418, 421 to 426, 428, 429, 431, 451418 is reserved, not assigned
500 to 599500 to 508, 510, 511510 is obsoleted
03

1xx Informational

100 Continue, 101 Switching Protocols, 102 Processing, 103 Early Hints, 104 Upload Resumption

100 Continue

everyday

The client asks before sending a large body

PUT /uploads/video.mp4 HTTP/1.1
Host: api.example.com
Content-Length: 734003200
Expect: 100-continue

HTTP/1.1 100 Continue

HTTP/1.1 201 Created
Location: /uploads/video.mp4

curl adds the header for large uploads

curl -T video.mp4 https://api.example.com/uploads/
curl -H "Expect:" -T video.mp4 https://api.example.com/uploads/    # never wait
curl --expect100-timeout 0.5 -T video.mp4 https://api.example.com/uploads/

101 Switching Protocols

everyday

Upgrading an HTTP/1.1 connection to a WebSocket

GET /chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

102 and 103

advanced

102 Processing is deprecated

HTTP/1.1 102 Processing

103 Early Hints lets the browser start loading assets early

HTTP/1.1 103 Early Hints
Link: </styles/main.css>; rel=preload; as=style
Link: <https://cdn.example.com>; rel=preconnect

HTTP/1.1 200 OK
Content-Type: text/html
Link: </styles/main.css>; rel=preload; as=style

104 Upload Resumption Supported is provisional

HTTP/1.1 104 Upload Resumption Supported
Location: https://example.com/upload/b530ce8ff
04

2xx Success

200, 201, 202, 204, 206 and the WebDAV success codes

200 OK

core

The default success, with a body

GET /users/42 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Content-Type: application/json

{"id": 42, "name": "Ana"}

A 200 that reports an error is a bug

HTTP/1.1 200 OK
Content-Type: application/json

{"success": false, "error": "User not found"}

201 Created

core

Return the new resource and its URL

POST /users HTTP/1.1
Content-Type: application/json

{"name": "Ana"}

HTTP/1.1 201 Created
Location: /users/43
Content-Type: application/json

{"id": 43, "name": "Ana"}

202 Accepted

everyday

Accept work that finishes later

POST /reports HTTP/1.1

HTTP/1.1 202 Accepted
Location: /reports/jobs/9f3a
Retry-After: 10

{"status": "queued", "check": "/reports/jobs/9f3a"}

204 No Content

core

Success with nothing to return

DELETE /users/43 HTTP/1.1

HTTP/1.1 204 No Content

206 Partial Content

everyday

Range requests resume downloads and stream video

GET /video.mp4 HTTP/1.1
Range: bytes=0-1048575

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1048575/73400320
Content-Length: 1048576
Accept-Ranges: bytes

Ask for the last bytes of a file

curl -s -r -16 -D - -o /dev/null https://httpbingo.org/range/1024 | grep -iE "^(HTTP|content-range)"

Rare 2xx Codes

advanced

203, 205, 207, 208 and 226

CodeNameWhere it appears
203Non-Authoritative Informationa proxy changed a 200 response from the origin
205Reset Contentthe client should clear the form that sent the request
207Multi-StatusWebDAV: an XML body holds a status per resource
208Already ReportedWebDAV bindings: a member was listed already
226IM Useddelta encoding: the body is a diff (RFC 3229)
05

3xx Redirection

301, 302, 303, 307, 308, 304 and which method a redirect keeps

The Redirect Codes Side by Side

core

Permanent or temporary, and does the method survive

CodePermanentPOST stays POSTUse it for
301 Moved Permanentlyyesno, clients may switch to GETa page or site moved for good
308 Permanent Redirectyesyesa moved API endpoint that receives POST or PUT
302 Foundnono, clients may switch to GETa short-term move of a page
307 Temporary Redirectnoyessending a POST somewhere else temporarily
303 See Othernoalways becomes GETshowing a result page after a form POST

Every redirect needs a Location header

HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Content-Length: 0

303 After a Form

everyday

Post, redirect, get

POST /orders HTTP/1.1
Content-Type: application/x-www-form-urlencoded

item=42&qty=1

HTTP/1.1 303 See Other
Location: /orders/8812

304 Not Modified

core

A conditional GET answered from the cache

GET /logo.png HTTP/1.1
If-None-Match: "33a64df5"

HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600

Redirect Loops

everyday

curl stops after too many hops

curl -sL --max-redirs 5 -o /dev/null -w "%{http_code} after %{num_redirects} hops\n" https://httpbingo.org/redirect/10; echo "exit $?"

The proxy loop, and its fix

# the app sees http because TLS ends at the load balancer, so it redirects forever
if ($http_x_forwarded_proto = "http") {
    return 301 https://$host$request_uri;
}

Rare and Retired 3xx

advanced

300, 305 and 306

CodeNameStatus
300Multiple Choicesvalid, but browsers do not offer the choice; rarely used
305Use Proxydeprecated: clients ignore it for security reasons
306(Unused)was Switch Proxy in a draft; reserved, never send it
06

400 to 409

bad request, authentication, permissions, not found, wrong method

400 Bad Request

core

The request cannot be understood

POST /users HTTP/1.1
Content-Type: application/json

{"name": "Ana",}

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{"title": "Malformed JSON", "status": 400, "detail": "Unexpected token } at position 15"}

401 Unauthorized or 403 Forbidden

core

401 means who are you, 403 means you may not

GET /admin/reports HTTP/1.1

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"

GET /admin/reports HTTP/1.1
Authorization: Bearer eyJhbGciOi...

HTTP/1.1 403 Forbidden

Hide a resource's existence with 404

GET /orgs/acme/secret-project HTTP/1.1
Authorization: Bearer eyJhbGciOi...

HTTP/1.1 404 Not Found

404 Not Found

core

Nothing at this URL, maybe ever

GET /products/does-not-exist HTTP/1.1

HTTP/1.1 404 Not Found
Content-Type: text/html
Cache-Control: max-age=300

405 Method Not Allowed

core

The URL exists, the method does not

DELETE /users HTTP/1.1

HTTP/1.1 405 Method Not Allowed
Allow: GET, POST

Other 40x Codes

everyday

402, 406, 407, 408

CodeNameWhen
402Payment Requiredreserved by the spec; some APIs use it for unpaid plans or exhausted credit
406Not Acceptablenothing matches the Accept header, for example Accept: application/xml on a JSON-only API
407Proxy Authentication Requireda proxy wants credentials; comes with Proxy-Authenticate
408Request Timeoutthe client was too slow sending the request; the server closes the connection

409 Conflict

core

The request clashes with the current state

POST /users HTTP/1.1
Content-Type: application/json

{"email": "ana@example.com"}

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{"title": "Email already registered", "status": 409}
07

410 to 418

gone, preconditions, size limits, media types, ranges and the teapot

410 Gone

everyday

Removed on purpose, not coming back

GET /promo/summer-2024 HTTP/1.1

HTTP/1.1 410 Gone

412 Precondition Failed

everyday

Someone else changed it first

PUT /docs/7 HTTP/1.1
If-Match: "v41"
Content-Type: application/json

{"title": "New title"}

HTTP/1.1 412 Precondition Failed
ETag: "v42"

Size and Format Limits

core

413, 414, 415 and 431

CodeNameTypical cause
413Content Too Largeupload over the limit, often nginx client_max_body_size, 1 MB by default
414URI Too Longa huge query string; move the data into a POST or QUERY body
415Unsupported Media Typewrong Content-Type, for example sending form data to a JSON endpoint
431Request Header Fields Too Largetoo many or oversized cookies or headers

Each library spells the reason phrase its own way

node -e "const h=require('http'); console.log(h.STATUS_CODES[413], '|', h.STATUS_CODES[422])"
python -c "from http import HTTPStatus as S; print(S(413).phrase, '|', S(422).phrase)"

416, 417 and 418

advanced

Range Not Satisfiable states the real size

GET /file.zip HTTP/1.1
Range: bytes=9000000-

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */1048576

418 is a joke the spec keeps reserved

curl -s -o /dev/null -w "%{http_code}\n" https://httpbingo.org/status/418
08

421 to 451

validation, rate limiting, locking, upgrades and legal blocks

422 Unprocessable Content

core

Well formed, but the values are wrong

POST /users HTTP/1.1
Content-Type: application/json

{"email": "not-an-email", "age": -3}

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://example.com/probs/validation",
  "title": "Validation failed",
  "status": 422,
  "errors": [
    {"field": "email", "detail": "must be a valid email address"},
    {"field": "age", "detail": "must be 0 or more"}
  ]
}

429 Too Many Requests

core

Rate limited, with a time to wait

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json

{"title": "Rate limit exceeded", "status": 429, "detail": "100 requests per minute"}

Some limits are not 429

HTTP/1.1 403 Forbidden
x-ratelimit-remaining: 0

HTTP/1.1 503 Service Unavailable
Retry-After: 120

Locking, Timing and Upgrades

advanced

421, 423, 424, 425, 426 and 428

CodeNameWhen
421Misdirected Requestan HTTP/2 connection was reused for a host this server does not serve
423LockedWebDAV: the resource is locked
424Failed DependencyWebDAV: an action failed because an earlier one did
425Too Earlythe server will not risk processing a TLS 1.3 early data request that could be replayed
426Upgrade Requireduse another protocol; the Upgrade header names it
428Precondition Requiredsend If-Match or similar to prevent lost updates

451 Unavailable for Legal Reasons

advanced

Blocked by law, with an optional pointer to who demanded it

HTTP/1.1 451 Unavailable For Legal Reasons
Link: <https://authority.example/>; rel="blocked-by"
Content-Type: text/html

<p>This content is not available in your country.</p>
09

5xx Server Errors

500, 502, 503, 504 and the rest, with what usually causes each

500 Internal Server Error

core

Something failed that the server did not expect

HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{"title": "Internal Server Error", "status": 500, "instance": "/orders/8812", "traceId": "4bf92f3577b34da6"}

502, 503 and 504

core

Three gateway errors that mean different things

CodeWho sends itWhat happened
502 Bad Gatewaya proxy or load balancerthe upstream server closed the connection, sent garbage or refused it
503 Service Unavailablethe server or a proxyoverloaded, in maintenance, or no healthy upstream; often temporary
504 Gateway Timeouta proxy or load balancerthe upstream server did not answer in time

503 with Retry-After for planned downtime

HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: text/html
Cache-Control: no-store

Rarer 5xx Codes

advanced

501, 505 to 511

CodeNameWhen
501Not Implementedthe server does not support the method at all, for example PROPFIND on a plain web server
505HTTP Version Not Supportedthe request used an HTTP version the server refuses
506Variant Also Negotiatesa content negotiation configuration error (RFC 2295, experimental)
507Insufficient StorageWebDAV: no space to store the result
508Loop DetectedWebDAV: infinite loop while processing a request with Depth: infinity
510Not Extendedobsolete: the HTTP extension framework of RFC 2774 is historic
511Network Authentication Requireda captive portal on hotel or airport Wi-Fi wants you to log in

510 is obsolete

HTTP/1.1 510 Not Extended
10

Unofficial Codes

nginx, Cloudflare, AWS load balancers, IIS and other codes outside the registry

nginx

everyday

Codes nginx uses internally and in its logs

CodeMeaning in nginx
444close the connection without sending any response (return 444;)
494request header too large, before nginx answers 400
495the client certificate failed verification
496the client did not send a required certificate
497a plain HTTP request reached the HTTPS port
499the client closed the connection before nginx answered; only seen in logs

Drop junk requests silently

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

Cloudflare

everyday

52x codes mean Cloudflare could not get a good answer from your origin

CodeCloudflare's nameLook at
520Web server returns an unknown errorthe origin sent an empty, invalid or reset response
521Web server is downthe origin refused the connection; is it running, is the firewall blocking Cloudflare IPs?
522Connection timed outTCP to the origin timed out
523Origin is unreachableDNS or routing to the origin is broken
524A timeout occurredthe origin accepted but did not answer within 100 seconds
525SSL handshake failedTLS between Cloudflare and the origin failed
526Invalid SSL certificateFull (strict) mode and an invalid origin certificate
530Origin errorshown together with a 1xxx Cloudflare error

AWS Application Load Balancer

advanced

Codes only an ALB generates

CodeMeaning
460the client closed the connection before the idle timeout
463X-Forwarded-For held more than 30 IP addresses
464the request protocol does not match the target group protocol version
561the identity provider returned an error during listener authentication
562the JWKS endpoint for JWT validation failed

Other Codes You May Meet

advanced

Framework and vendor codes outside the registry

CodeSourceMeaning
419LaravelPage Expired: the CSRF token is missing or stale
420old Twitter APIEnhance Your Calm, an early rate limit code; use 429
440IIS, ExchangeLogin Time-out
449IISRetry With
509cPanel, Apache modulesBandwidth Limit Exceeded
598, 599some proxiesnetwork read or connect timeout
11

Choosing the Right Code

decision tables for the cases people argue about

The Usual Debates

core

Pick by what the client can do next

SituationUseNot
not logged in, or the token expired401403
logged in, but no permission403401
hide that the thing exists404403
JSON is broken400422
JSON is fine, the values are invalid422 or 400500
email already registered409400
stale version in If-Match412409
deleted for good410404, though both work
created something201 with Location200
worked, nothing to return204200 with an empty body
work queued for later202200
quota exceeded429403
maintenance or overload503500

Redirect Decisions

core

Four questions, one code

Is the move permanent?Must the method and body survive?Code
yesno, these are pages for GET301
yesyes, an API endpoint308
nono302
noyes307
after a POST, show a result pageit becomes GET on purpose303

Error Codes for Each Layer

everyday

Who should answer with what

LayerTypical codes
reverse proxy or CDN502, 503, 504, 413, 414, 431
authentication middleware401, 403, 429
routing404, 405, 406, 415
validation400, 422
business logic409, 412, 410, 402
unexpected failure500
12

Status Codes in REST APIs

what each HTTP method returns on success and on error

Method by Method

core

Success and common error codes for CRUD

MethodSuccessCommon errors
GET /items200400, 401, 403
GET /items/7200, 304404, 410
POST /items201 with Location, or 202400, 409, 415, 422
PUT /items/7200 or 204; 201 if it created the item400, 404, 409, 412, 422
PATCH /items/7200 or 204400, 404, 409, 412, 415, 422
DELETE /items/7204, 200 with a body, or 202404, 409
HEAD /items/7200, 304404
OPTIONS /items204 or 200 with Allow405

DELETE twice: 204, then 404

DELETE /items/7 HTTP/1.1

HTTP/1.1 204 No Content

DELETE /items/7 HTTP/1.1

HTTP/1.1 404 Not Found

Bulk and Async Work

advanced

Report a result per item

POST /items/batch HTTP/1.1

HTTP/1.1 207 Multi-Status
Content-Type: application/json

{"results": [
  {"id": 1, "status": 201},
  {"id": 2, "status": 422, "detail": "price must be positive"}
]}

Poll a job until it finishes

GET /jobs/9f3a HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{"status": "running", "progress": 0.4}

GET /jobs/9f3a HTTP/1.1

HTTP/1.1 303 See Other
Location: /reports/2026-10

Health Checks

everyday

What load balancers and Kubernetes expect

GET /healthz HTTP/1.1

HTTP/1.1 200 OK
Content-Type: text/plain

ok
13

Error Bodies

RFC 9457 problem details, validation errors and what to put in an error

Problem Details, RFC 9457

core

One JSON shape for every error

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "status": 403,
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30
}

The members

MemberTypeMeaning
typeURI referenceidentifies the problem type; about:blank if omitted
titlestringshort, human readable summary of the type
statusnumberthe HTTP status code, repeated for convenience
detailstringexplanation of this occurrence, for humans
instanceURI referenceidentifies this occurrence
anything elseanyextension members, such as errors or traceId

Validation Errors

everyday

List every invalid field at once

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://example.com/probs/validation",
  "title": "Your request is not valid.",
  "status": 422,
  "errors": [
    {"pointer": "#/email", "detail": "must be a valid email address"},
    {"pointer": "#/items/0/qty", "detail": "must be at least 1"}
  ]
}

Built-in Problem Details

everyday

Frameworks that emit RFC 9457 for you

// ASP.NET Core: problem details for exceptions and empty error responses
builder.Services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();

Spring Boot

spring.mvc.problemdetails.enabled=true

What Not to Put in an Error

core

Leaks and dead ends

HTTP/1.1 500 Internal Server Error

{"error": "SQLSTATE[42S02]: Base table 'prod.users_v2' doesn't exist at /var/www/app/db.php:88"}
14

Headers That Travel With Codes

the headers a status code requires or expects

Required and Expected Headers

core

Code, header, and how strict the spec is

CodeHeaderRule
201Locationexpected: the URL of the new resource
3xx redirectsLocationneeded, or there is nowhere to go
401WWW-Authenticatemust: names the auth scheme
405Allowmust: lists the supported methods
407Proxy-Authenticatemust
206Content-Rangemust, per part
416Content-Rangeshould: bytes */size
429, 503Retry-Aftershould, when you know how long
101, 426Upgrademust: names the protocol
204, 304(no body)must not send content

Retry-After

core

Two formats, both valid

HTTP/1.1 503 Service Unavailable
Retry-After: 120

HTTP/1.1 429 Too Many Requests
Retry-After: Wed, 14 Oct 2026 18:30:00 GMT

Deprecating an Endpoint

advanced

Deprecation and Sunset headers

HTTP/1.1 200 OK
Deprecation: @1798761600
Sunset: Sat, 01 May 2027 00:00:00 GMT
Link: <https://api.example.com/v2/orders>; rel="successor-version"

Discovery Headers

advanced

Tell clients what they can send

HTTP/1.1 415 Unsupported Media Type
Accept-Post: application/json, application/merge-patch+json

HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, QUERY
Accept-Query: application/sql, application/jsonpath
15

Caching & Conditional Requests

which codes caches keep, ETag, 304 and 412

Cacheable by Default

everyday

Caches may store these without explicit Cache-Control

CodesHeuristically cacheable
200, 203, 204, 206yes
300, 301, 308yes
404, 405, 410, 414yes
501yes
302, 303, 307only with explicit freshness, such as Cache-Control: max-age
anything elseonly with explicit freshness

Stop a redirect or error from being cached

HTTP/1.1 301 Moved Permanently
Location: https://example.com/new
Cache-Control: no-store

HTTP/1.1 404 Not Found
Cache-Control: max-age=60

ETag and 304

core

First request, then a conditional one

GET /api/config HTTP/1.1

HTTP/1.1 200 OK
ETag: "a1b2c3"
Cache-Control: no-cache

GET /api/config HTTP/1.1
If-None-Match: "a1b2c3"

HTTP/1.1 304 Not Modified
ETag: "a1b2c3"

See a 304 with curl

curl -s -o /dev/null -w "%{http_code}\n" -H 'If-None-Match: "abc"' "https://httpbingo.org/etag/abc"

Conditional Writes

everyday

If-Match turns lost updates into 412

HeaderSent withFails with
If-None-MatchGET, HEAD304 when the ETag matches
If-Modified-SinceGET, HEAD304 when not modified
If-MatchPUT, PATCH, DELETE412 when the ETag differs
If-Unmodified-SincePUT, PATCH, DELETE412 when modified since
If-None-Match: *PUT412 when the resource already exists
16

Retries & Rate Limits

which codes to retry, Retry-After and backoff with jitter

Retry or Not

core

A retry policy by status code

CodeRetry?How
408, 425yesat once, once
429yesafter Retry-After, or with backoff
500maybeonly idempotent requests, with backoff
502, 503, 504yeswith backoff; honor Retry-After on 503
other 4xxnothe request itself is wrong
network error, no responseyes for idempotent methodswith backoff

Exponential Backoff With Jitter

everyday

Wait longer after each failure, with randomness

const RETRY = new Set([408, 425, 429, 500, 502, 503, 504]);

async function fetchWithRetry(url, options = {}, tries = 5) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, options);
    if (!RETRY.has(res.status) || attempt === tries - 1) return res;
    const header = res.headers.get("retry-after");
    let wait = Math.min(30000, 500 * 2 ** attempt) * Math.random();
    if (header) {
      const secs = Number(header);
      wait = Number.isNaN(secs) ? Date.parse(header) - Date.now() : secs * 1000;
    }
    await new Promise((r) => setTimeout(r, Math.max(0, wait)));
  }
}

Rate Limit Headers

everyday

The common header families

HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4987
X-RateLimit-Reset: 1792000000

HTTP/1.1 200 OK
RateLimit-Policy: "default";q=100;w=60
RateLimit: "default";r=37;t=21
17

Redirects, 404s & SEO

how Google treats each code, soft 404s and maintenance

How Google Reads Status Codes

core

What each class does to indexing

CodeEffect in Google Search
200content goes to indexing; a 200 that looks like an error page is a soft 404
301, 308strong signal that the target is the canonical URL
302, 303, 307weak signal; the original URL may stay indexed
304no change; Google keeps what it has
404, 410treated the same: dropped from the index over time
429, 5xxcrawling slows down; pages that keep failing are dropped

Soft 404s

everyday

A missing page must not answer 200

HTTP/1.1 200 OK
Content-Type: text/html

<h1>Sorry, we could not find that product</h1>

Sitemaps and Canonical URLs

everyday

Every URL in a sitemap should answer 200

curl -s https://example.com/sitemap.xml | grep -o "<loc>[^<]*" | cut -c6- | head -20 | xargs -I {} curl -s -o /dev/null -w "%{http_code} {}\n" {}

robots.txt Errors

advanced

Its status code changes how the whole site is crawled

robots.txt answersGoogle does
2xxfollows the rules in the file
3xxfollows at least five hops, then treats it as 404
4xx except 429assumes there are no crawl restrictions
5xx or 429stops crawling the site for 12 hours, then uses a cached copy for up to 30 days
18

curl & the Command Line

status codes in scripts, --fail, --write-out and redirects

Status Codes in Scripts

core

Fail the script on 4xx and 5xx

curl -sf https://httpbingo.org/status/503 > /dev/null; echo "exit $?"

Branch on the code in bash

code=$(curl -s -o /dev/null -w "%{http_code}" https://httpbingo.org/status/201)
case "$code" in
  2??) echo "ok: $code" ;;
  4??) echo "client error: $code" ;;
  5??) echo "server error: $code" ;;
  *)   echo "no HTTP response: $code" ;;
esac

Retry transient errors automatically

curl --retry 5 --retry-max-time 60 -sf https://api.example.com/health

Redirects in curl

everyday

Where would this redirect go

curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" "https://httpbingo.org/redirect-to?url=https://example.com/&status_code=308"

Count the hops and print the final URL

curl -sL -o /dev/null -w "%{num_redirects} hops, final %{http_code} at %{url_effective}\n" https://httpbingo.org/redirect/3

Many URLs at Once

advanced

Check a list of URLs in parallel

xargs -P 8 -I {} curl -s -o /dev/null -w "%{http_code} {}\n" {} < urls.txt | sort
19

fetch, Browsers & DevTools

response.ok, redirects, CORS and reading codes in DevTools

fetch() and Status Codes

core

fetch resolves on 404 and 500

const res = await fetch("https://httpbingo.org/status/404");
console.log(res.status, res.ok, res.statusText);

const r2 = await fetch("https://httpbingo.org/status/204");
console.log(r2.status, r2.ok);

Turn HTTP errors into exceptions

async function getJson(url) {
  const res = await fetch(url, { headers: { Accept: "application/json" } });
  if (!res.ok) {
    const body = await res.text();
    throw new Error(`HTTP ${res.status} for ${url}: ${body.slice(0, 200)}`);
  }
  return res.status === 204 ? null : res.json();
}

statusText is empty over HTTP/2 and HTTP/3

console.log(res.status, JSON.stringify(res.statusText));   // 404 ""

Redirects in fetch

everyday

Browsers hide manual redirects, Node.js does not

const res = await fetch("https://httpbingo.org/redirect-to?url=/get&status_code=302", { redirect: "manual" });
console.log(res.status, res.type, res.headers.get("location"));

const followed = await fetch("https://httpbingo.org/redirect/2");
console.log(followed.status, followed.redirected, followed.url);

CORS and Status 0

everyday

A CORS failure has no status at all

fetch("https://other-site.example/api")
  .then((res) => console.log(res.status))
  .catch((err) => console.log(err.name, err.message));
// TypeError Failed to fetch

DevTools Network Tab

core

Reading codes in the browser

ShownMeaning
200 (from disk cache)no request was sent; the cached copy was used
200 (from memory cache)same, from this page's memory cache
304revalidated with the server, body from cache
(canceled) or (failed)no status: aborted, blocked or a network error
CORS errorthe response arrived but JavaScript may not read it
307 Internal RedirectChrome upgrading http to https by HSTS, not a server response
20

Setting Codes in Code

Express, Python, Spring, Go, PHP, .NET and Rails side by side

Created, With a Location

core

Express

app.post("/users", (req, res) => {
  const user = createUser(req.body);
  res.status(201).location(`/users/${user.id}`).json(user);
});

Next.js App Router route handler

export async function POST(request) {
  const user = await createUser(await request.json());
  return Response.json(user, { status: 201, headers: { Location: `/api/users/${user.id}` } });
}

Flask and FastAPI

# Flask: return body, status, headers
@app.post("/users")
def create_user():
    user = save(request.get_json())
    return user, 201, {"Location": f"/users/{user['id']}"}

# FastAPI: declare the success code on the route
@app.post("/users", status_code=status.HTTP_201_CREATED)
def create_user(user: UserIn, response: Response):
    saved = save(user)
    response.headers["Location"] = f"/users/{saved.id}"
    return saved

Spring Boot

@PostMapping("/users")
ResponseEntity<User> create(@RequestBody User user) {
    User saved = repository.save(user);
    return ResponseEntity.created(URI.create("/users/" + saved.getId())).body(saved);
}

Go 1.22 and later

mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
	user := createUser(r)
	w.Header().Set("Location", fmt.Sprintf("/users/%d", user.ID))
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(user)
})

PHP and Laravel

// plain PHP
http_response_code(201);
header("Location: /users/{$user['id']}");
header('Content-Type: application/json');
echo json_encode($user);

// Laravel
return response()->json($user, 201)->header('Location', "/users/{$user->id}");

ASP.NET Core minimal API

app.MapPost("/users", (UserIn input, AppDb db) =>
{
    var user = db.Add(input);
    return TypedResults.Created($"/users/{user.Id}", user);
});

Ruby on Rails

def create
  @user = User.create!(user_params)
  render json: @user, status: :created, location: @user
end

Not Found and Other Errors

core

Express

app.get("/users/:id", (req, res) => {
  const user = findUser(req.params.id);
  if (!user) return res.status(404).json({ title: "User not found", status: 404 });
  res.json(user);
});
app.delete("/users/:id", (req, res) => { removeUser(req.params.id); res.sendStatus(204); });

Flask, FastAPI and Django

abort(404)                                             # Flask
raise HTTPException(status_code=404, detail="User not found")  # FastAPI
raise Http404("User not found")                        # Django
return JsonResponse({"error": "conflict"}, status=409) # Django

Spring Boot

@GetMapping("/users/{id}")
User get(@PathVariable long id) {
    return repository.findById(id)
        .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
}

@DeleteMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
void delete(@PathVariable long id) { repository.deleteById(id); }

Go

http.Error(w, "user not found", http.StatusNotFound)

PHP and Laravel

http_response_code(404);   // plain PHP
abort(404);                // Laravel
abort(403, 'Not allowed');
return response()->noContent();   // 204

ASP.NET Core

app.MapGet("/users/{id}", (int id, AppDb db) =>
    db.Find(id) is { } user ? Results.Ok(user) : Results.NotFound());

app.MapGet("/status", () => Results.Problem(statusCode: 503, title: "Down for maintenance"));

Rails

head :no_content
render json: { error: "conflict" }, status: :conflict
raise ActiveRecord::RecordNotFound   # rendered as 404 in production

Redirects and Their Default Codes

everyday

Express and Next.js

res.redirect("/login");            // 302 by default
res.redirect(301, "/new-page");
// Next.js: redirect() sends 307, permanentRedirect() sends 308

Flask, FastAPI and Django

return redirect("/new-page", code=301)          # Flask, default 302
return RedirectResponse("/new-page", status_code=301)  # FastAPI, default 307
return redirect("/new-page", permanent=True)    # Django, default 302

Spring

return "redirect:/orders/" + id;        // 302 from a @Controller
return ResponseEntity.status(HttpStatus.SEE_OTHER).location(URI.create("/orders/" + id)).build();

Go

http.Redirect(w, r, "/new-page", http.StatusMovedPermanently)

PHP and Laravel

header('Location: /new-page', true, 301);   // without a code PHP sends 302
exit;
return redirect('/new-page', 301);           // Laravel, default 302

ASP.NET Core

Results.Redirect("/new-page");                       // 302
Results.Redirect("/new-page", permanent: true);      // 301
Results.Redirect("/new-page", permanent: true, preserveMethod: true);  // 308

Rails

redirect_to "/new-page"                               # 302
redirect_to "/new-page", status: :moved_permanently  # 301
redirect_to order_path(@order), status: :see_other   # 303, after a form

Codes Without a Framework

everyday

Node.js http sends the reason phrase for you

const http = require("http");
const server = http.createServer((req, res) => {
  res.writeHead(418, { "Content-Type": "text/plain" });
  res.end("short and stout");
});
server.listen(0, async () => {
  const res = await fetch(`http://localhost:${server.address().port}/`);
  console.log(res.status, res.statusText, await res.text());
  server.close();
});

Python urllib raises on 4xx and 5xx

import urllib.request, urllib.error

try:
    urllib.request.urlopen("https://httpbingo.org/status/404")
except urllib.error.HTTPError as e:
    print(e.code, e.reason)

from http import HTTPStatus
s = HTTPStatus(429)
print(s.value, s.phrase, s.is_client_error, s.is_server_error)
21

Testing Status Codes

asserting codes in unit, integration and load tests

Integration Tests

core

node:test with fetch, no extra packages

const test = require("node:test");
const assert = require("node:assert");
const http = require("http");

const app = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/items") {
    res.writeHead(201, { Location: "/items/42" });
    return res.end();
  }
  res.writeHead(404).end();
});

test("POST /items answers 201 with a Location", async (t) => {
  await new Promise((resolve) => app.listen(0, resolve));
  t.after(() => app.close());
  const base = `http://localhost:${app.address().port}`;
  const res = await fetch(`${base}/items`, { method: "POST" });
  assert.strictEqual(res.status, 201);
  assert.strictEqual(res.headers.get("location"), "/items/42");
  assert.strictEqual((await fetch(`${base}/nope`)).status, 404);
});

pytest with the Flask or FastAPI test client

def test_create_item(client):
    r = client.post("/items", json={"name": "lamp"})
    assert r.status_code == 201
    assert r.headers["Location"] == "/items/42"

def test_missing_item(client):
    assert client.get("/items/999").status_code == 404

Spring MockMvc

mockMvc.perform(post("/items")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"name\":\"lamp\"}"))
    .andExpect(status().isCreated())
    .andExpect(header().string("Location", "/items/42"));

mockMvc.perform(get("/items/999")).andExpect(status().isNotFound());

Playwright API checks

test("create and read an item", async ({ request }) => {
  const created = await request.post("/api/items", { data: { name: "lamp" } });
  expect(created.status()).toBe(201);
  const item = await request.get(created.headers()["location"]);
  await expect(item).toBeOK();
});

Python requests

everyday

Check the code, then raise for errors

import requests

r = requests.get("https://httpbingo.org/status/503", timeout=10)
print(r.status_code, r.ok, r.reason)
try:
    r.raise_for_status()
except requests.HTTPError as e:
    print("HTTPError:", e)

Postman and Load Tests

everyday

Postman test script

pm.test("created", () => {
  pm.response.to.have.status(201);
  pm.response.to.have.header("Location");
});

k6 load test with a status check

import http from "k6/http";
import { check } from "k6";

export default function () {
  const res = http.get("https://test.k6.io/");
  check(res, { "status is 200": (r) => r.status === 200 });
}
22

nginx, Apache & Caddy

redirects, error pages, size limits and rate limits in the web server

nginx

core

Redirects

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

location = /old-page { return 301 /new-page; }
rewrite ^/blog/(.*)$ /articles/$1 permanent;   # 301; redirect flag gives 302

Error pages and changed codes

error_page 404 /errors/404.html;
error_page 502 503 504 /errors/50x.html;
location /errors/ { internal; }

error_page 404 =200 /empty.gif;     # answer 200 instead
proxy_intercept_errors on;          # also use them for upstream errors

Size limits, rate limits and timeouts

client_max_body_size 50m;            # default 1m, beyond it 413

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;            # default 503
    proxy_read_timeout 120s;         # default 60s, then 504
    proxy_pass http://app:8080;
}

Apache

everyday

Redirects in .htaccess

Redirect 301 /old-page /new-page
RedirectMatch 301 ^/blog/(.*)$ /articles/$1

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Error documents

ErrorDocument 404 /errors/404.html
ErrorDocument 500 "Something went wrong. Please try again later."

Caddy

everyday

Redirects, errors and fixed responses

example.com {
    redir /old-page /new-page 301
    respond /healthz 200
    handle_errors {
        rewrite * /errors/{err.status_code}.html
        file_server
    }
    reverse_proxy app:8080
}
23

Proxies, Load Balancers & CDNs

where 502, 503 and 504 really come from

Who Sent This Error

core

Read the response headers, not just the code

HTTP/1.1 502 Bad Gateway
Server: cloudflare
CF-RAY: 8d2c1f0e9a7b1234-LHR

HTTP/1.1 504 Gateway Time-out
Server: nginx/1.28.0
Content-Type: text/html

<html><body><center><h1>504 Gateway Time-out</h1></center><hr><center>nginx/1.28.0</center></body></html>

nginx Upstream Errors

everyday

Match the error log line to the fix

error.log saysClient getsUsual fix
connect() failed (111: Connection refused)502the app is down or listens on another port or address
no live upstreams502every upstream server failed its checks
upstream prematurely closed connection502the app crashed mid-response or closed a keep-alive connection
upstream sent too big header502raise proxy_buffer_size for big cookies or headers
upstream timed out (110) while reading response header504the app is slow; speed it up or raise proxy_read_timeout
client intended to send too large body413raise client_max_body_size

499 and 504 Together

advanced

Two views of the same slow request

# nginx access log
10.0.0.5 - - [14/Oct/2026:18:22:41 +0000] "GET /report HTTP/1.1" 499 0 "-" "curl/8.19.0"
24

HTTP/2, HTTP/3 & Other Protocols

:status, gRPC, GraphQL and WebSocket codes

HTTP/2 and HTTP/3

everyday

The code arrives as the :status pseudo-header

const http2 = require("http2");
const client = http2.connect("https://github.com");
const req = client.request({ ":path": "/this-page-does-not-exist" });
req.on("response", (headers) => {
  console.log(headers[":status"], headers["content-type"]);
  client.close();
});
req.end();

curl prints the protocol and no reason phrase

$ curl -sI https://github.com | head -1
HTTP/2 200

gRPC Status Codes

advanced

gRPC answers HTTP 200 and puts its own code in a trailer

gRPC codeNumberHTTP equivalent
OK0200
CANCELLED1499
INVALID_ARGUMENT3400
DEADLINE_EXCEEDED4504
NOT_FOUND5404
ALREADY_EXISTS6409
PERMISSION_DENIED7403
RESOURCE_EXHAUSTED8429
FAILED_PRECONDITION9400
ABORTED10409
UNIMPLEMENTED12501
INTERNAL13500
UNAVAILABLE14503
UNAUTHENTICATED16401

GraphQL and WebSockets

advanced

GraphQL errors often travel in a 200

HTTP/1.1 200 OK
Content-Type: application/json

{"data": {"user": null}, "errors": [{"message": "User not found", "path": ["user"]}]}

WebSocket close codes are not HTTP codes

Close codeMeaning
1000normal closure
1001going away: page closed or server restarting
1006abnormal closure, no close frame (set by the client, never sent)
1008policy violation
1009message too big
1011internal server error
25

Monitoring & Logs

counting codes in logs, 5xx rates, alerts and error budgets

Count Codes in an Access Log

core

Which codes, how often

awk '{print $9}' access.log | sort | uniq -c | sort -rn | head -8

The share of server errors

awk '{n++} $9 ~ /^5/ {e++} END {printf "%d of %d requests were 5xx (%.1f%%)\n", e, n, 100 * e / n}' access.log

The URLs behind the errors

awk '$9 >= 500 {print $9, $7}' access.log | sort | uniq -c | sort -rn

Prometheus and Alerts

everyday

5xx ratio over five minutes

sum(rate(http_requests_total{code=~"5.."}[5m]))
  /
sum(rate(http_requests_total[5m]))

An alerting rule

groups:
  - name: http
    rules:
      - alert: HighErrorRate
        expr: sum(rate(http_requests_total{code=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.02
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "More than 2% of requests fail with 5xx"

Error Budgets

advanced

What 99.9% allows

Availability targetFailed requests allowedFull downtime per 30 days
99%1 in 1007.2 hours
99.9%1 in 1,00043.2 minutes
99.95%1 in 2,00021.6 minutes
99.99%1 in 10,0004.3 minutes
26

Troubleshooting by Code

a checklist for the errors that cost the most time

Client Errors

core

First checks for 4xx

CodeCheck first
400malformed JSON, wrong Content-Type, oversized cookies, a typo in a query parameter
401missing or expired token, wrong auth scheme, clock skew breaking JWT validation
403file permissions on static files, a WAF rule, CORS preflight blocked, wrong role
404trailing slash, case sensitivity, SPA routes without a server fallback, wrong base path behind a proxy
405form posting to a static file server, a missing route for that method
413client_max_body_size in nginx, upload limits in PHP (upload_max_filesize) or the framework
415Content-Type header missing or not application/json
429client loops without backoff, a shared IP behind NAT hitting a per-IP limit

Server Errors

core

First checks for 5xx

CodeCheck first
500the application log and the stack trace for that request ID
502is the app running and listening where the proxy expects; proxy error log
503maintenance mode, no healthy targets, connection pool or queue exhausted
504slow queries, slow third-party calls, proxy timeouts shorter than the work
27

Specs & History

the RFCs that define status codes, from 1996 to 2026

Where the Codes Are Defined

everyday

The documents that matter

YearDocumentStatus codes
1996RFC 1945, HTTP/1.0the first 15: 200, 201, 202, 204, 301, 302, 304, 400, 401, 403, 404, 500, 501, 502, 503
1997 and 1999RFC 2068 and RFC 2616, HTTP/1.1most of today's list, including 100, 101, 307 and 409 to 417
2012RFC 6585428, 429, 431, 511
2015RFC 7538308 Permanent Redirect
2016RFC 7725451 Unavailable For Legal Reasons
2017RFC 8297103 Early Hints
2018RFC 8470425 Too Early
2022RFC 9110, HTTP Semanticsthe current definition of the core codes; adds 421 and 422 to the core
2026RFC 10008the QUERY method; no new codes, but it defines how QUERY uses 303 and Location

Renamed and Retired

advanced

Names changed in RFC 9110 and before

CodeOld nameCurrent name
413Request Entity Too Large, then Payload Too LargeContent Too Large
414Request-URI Too LongURI Too Long
416Requested Range Not SatisfiableRange Not Satisfiable
422Unprocessable EntityUnprocessable Content
418I'm a teapot (RFC 2324)(Unused), reserved
306Switch Proxy (draft)(Unused)
28

Every Registered Code

the full IANA registry in one table

The IANA Registry, October 2026

core

All registered codes, with the document that defines each

CodeNameDefined inCacheable by default
100ContinueRFC 9110, Section 15.2.1
101Switching ProtocolsRFC 9110, Section 15.2.2
102ProcessingRFC 2518
103Early HintsRFC 8297
104Upload Resumption Supported, temporary until 2026-11-13draft-ietf-httpbis-resumable-upload-05
200OKRFC 9110, Section 15.3.1yes
201CreatedRFC 9110, Section 15.3.2
202AcceptedRFC 9110, Section 15.3.3
203Non-Authoritative InformationRFC 9110, Section 15.3.4yes
204No ContentRFC 9110, Section 15.3.5yes
205Reset ContentRFC 9110, Section 15.3.6
206Partial ContentRFC 9110, Section 15.3.7yes
207Multi-StatusRFC 4918
208Already ReportedRFC 5842
226IM UsedRFC 3229
300Multiple ChoicesRFC 9110, Section 15.4.1yes
301Moved PermanentlyRFC 9110, Section 15.4.2yes
302FoundRFC 9110, Section 15.4.3
303See OtherRFC 9110, Section 15.4.4
304Not ModifiedRFC 9110, Section 15.4.5
305Use ProxyRFC 9110, Section 15.4.6
306(Unused)RFC 9110, Section 15.4.7
307Temporary RedirectRFC 9110, Section 15.4.8
308Permanent RedirectRFC 9110, Section 15.4.9yes
400Bad RequestRFC 9110, Section 15.5.1
401UnauthorizedRFC 9110, Section 15.5.2
402Payment RequiredRFC 9110, Section 15.5.3
403ForbiddenRFC 9110, Section 15.5.4
404Not FoundRFC 9110, Section 15.5.5yes
405Method Not AllowedRFC 9110, Section 15.5.6yes
406Not AcceptableRFC 9110, Section 15.5.7
407Proxy Authentication RequiredRFC 9110, Section 15.5.8
408Request TimeoutRFC 9110, Section 15.5.9
409ConflictRFC 9110, Section 15.5.10
410GoneRFC 9110, Section 15.5.11yes
411Length RequiredRFC 9110, Section 15.5.12
412Precondition FailedRFC 9110, Section 15.5.13
413Content Too LargeRFC 9110, Section 15.5.14
414URI Too LongRFC 9110, Section 15.5.15yes
415Unsupported Media TypeRFC 9110, Section 15.5.16
416Range Not SatisfiableRFC 9110, Section 15.5.17
417Expectation FailedRFC 9110, Section 15.5.18
418(Unused)RFC 9110, Section 15.5.19
421Misdirected RequestRFC 9110, Section 15.5.20
422Unprocessable ContentRFC 9110, Section 15.5.21
423LockedRFC 4918
424Failed DependencyRFC 4918
425Too EarlyRFC 8470
426Upgrade RequiredRFC 9110, Section 15.5.22
428Precondition RequiredRFC 6585
429Too Many RequestsRFC 6585
431Request Header Fields Too LargeRFC 6585
451Unavailable For Legal ReasonsRFC 7725
500Internal Server ErrorRFC 9110, Section 15.6.1
501Not ImplementedRFC 9110, Section 15.6.2yes
502Bad GatewayRFC 9110, Section 15.6.3
503Service UnavailableRFC 9110, Section 15.6.4
504Gateway TimeoutRFC 9110, Section 15.6.5
505HTTP Version Not SupportedRFC 9110, Section 15.6.6
506Variant Also NegotiatesRFC 2295
507Insufficient StorageRFC 4918
508Loop DetectedRFC 5842
510Not Extended (OBSOLETED)RFC 2774, Status change of HTTP experiments to Historic
511Network Authentication RequiredRFC 6585

What Is an HTTP Status Codes Cheat Sheet

An HTTP status codes cheat sheet is a compact reference to the three-digit codes servers send back with every response, arranged so you can see in seconds what a code means, which headers belong with it and what the client should do next. People also search for a list of HTTP status codes or HTTP response codes; they usually need one at two moments: while designing an API, and while staring at a 502 at two in the morning.

This cheat sheet covers 28 sections and 156 snippets: every code in the IANA registry, the unofficial codes that nginx, Cloudflare and AWS load balancers add, and the practical side of using them, from choosing between 401 and 403 to setting a 201 in eleven frameworks. Rows about vendor codes carry a chip, and the standard codes only button hides them. The output under the curl, Node.js and Python examples is what they printed when run on October 14, 2026.

The toolbox at the top checks a raw response or the output of curl -i and curl -iL against the rules of RFC 9110, follows redirect chains, looks up any code by number, range or name, suggests a code for a situation described in plain words, and tells you whether a client should retry. The builders write RFC 9457 error bodies, redirect rules for nine servers and platforms, status code handlers for eleven frameworks and custom error page configuration. Everything runs in your browser.

How It Differs From the RFCs

RFC 9110 defines the semantics precisely; this page puts each code next to the mistake that usually comes with it: the 200 with an error in the body, the 401 without WWW-Authenticate, the permanent redirect cached by every browser during testing, the 500 that leaks a stack trace. For designing the APIs that send these codes, see the REST API cheat sheet; for the data format most API errors use, the JSON cheat sheet; for scripting the curl checks, the bash cheat sheet.


What This HTTP Status Codes Cheat Sheet Covers

The 28 sections go from reading a status line to debugging a gateway timeout, and each link jumps to that section above.


HTTP Status Codes Quick Reference

The decisions people look up most often, one line each. The sections above explain every one of them with a full example.

TaskCode
Return data200 OK
Created a resource201 Created plus Location
Queued for later202 Accepted
Success, no body204 No Content
Moved for good301, or 308 to keep the method
Moved for now302, or 307 to keep the method
Result page after a form303 See Other
Cached copy is fine304 Not Modified
Malformed request400 Bad Request
Not logged in401 plus WWW-Authenticate
Logged in, not allowed403 Forbidden
Nothing here404 Not Found
Wrong method405 plus Allow
Duplicate or state clash409 Conflict
Stale version412 Precondition Failed
Invalid values422 Unprocessable Content
Rate limited429 plus Retry-After
Unexpected crash500 Internal Server Error
Maintenance503 plus Retry-After
Error body formatapplication/problem+json

What an HTTP Status Code Is

Every HTTP response starts with a status code, a three-digit integer from 100 to 599 that tells the client how the server handled the request. HTTP/1.1 sends it on the first line together with a reason phrase; HTTP/2 and HTTP/3 send it as the :status pseudo-header and drop the phrase. Software acts on the number alone.

The first digit sets the class, and the class is the part every client must understand: an unknown 4xx code is handled like 400, an unknown 5xx like 500. That rule is why new codes such as 103 Early Hints or 425 Too Early could be added without breaking old software. The current definitions come from RFC 9110, published in June 2022, and IANA keeps the official registry, which this page reproduces in full.

Status codes matter most in places where software reads them without a person looking: caches decide what to store, search engines decide what to index, retry logic decides whether to try again, and monitoring decides whether to wake someone up. A wrong code misleads all four at once, which is why back-end development teams treat them as part of the API contract.

Where They Show Up

You meet status codes in browser DevTools, in curl output, in web server and load balancer logs, in API client libraries and in API integration work, where a partner's 429 or 503 decides how your own code behaves. They also shape communication between services in a microservices system, where one wrong 500 can trigger retries across a whole chain of calls.


How a Request Gets Its Status Code

A response often passes through several layers, and any of them can set the code you finally see.

  1. The client sends the request, possibly through a CDN such as Cloudflare and a load balancer, each of which can answer on its own with 4xx limits or 5xx gateway errors.
  2. A reverse proxy such as nginx applies its own rules: body size limits (413), rate limits (503 or 429), redirects and timeouts (504).
  3. The application's router picks a handler or answers 404 or 405 itself.
  4. Middleware checks authentication (401), permissions (403) and the request format (400, 415), and validation returns 422 or 400.
  5. The handler runs and returns a success code, a business error such as 409, or throws, which the framework turns into 500.
  6. On the way back, proxies may replace error pages, add caching headers or turn a broken upstream connection into 502.

The layer that produced a response leaves traces in headers such as Server, Via and CF-RAY, which is the first thing to check when a code makes no sense.


Status Codes Developers Confuse

The pairs that cause the most arguments in code reviews, and the question that settles each.

PairAskAnswer
401 or 403Would logging in help?yes: 401, no: 403
400 or 422Could the server parse it?no: 400, yes: 422
404 or 410Was it removed on purpose, for good?yes: 410, unknown: 404
409 or 412Did the client send If-Match?yes: 412, no: 409
301 or 308Must POST stay POST?yes: 308, no: 301
500 or 503Is it temporary and expected?yes: 503, no: 500
502 or 504Did the upstream answer at all?badly: 502, too late: 504
200 or 204Is there a body?yes: 200, no: 204

Common Status Code Mistakes

The same handful of problems shows up in most API reviews and incident reports.

In APIs

  • Returning 200 with an error message in the body, which hides failures from caches, clients and monitoring.
  • Sending 401 without a WWW-Authenticate header, or using 401 when the user is logged in but lacks permission.
  • Using 500 for invalid input, which sends people hunting for a server bug that is really a client mistake.
  • Forgetting Location on 201 and on redirects, and Retry-After on 429 and 503.
  • Leaking stack traces and SQL errors in 500 responses.

On Websites

  • Testing with 301 and getting a redirect stuck in your own browser cache; test with 302 or 307 first.
  • Serving a not found page with 200, which search engines report as a soft 404.
  • Chaining redirects, such as http to www to https to the final page, instead of one hop.
  • Showing a maintenance page with 200 instead of 503, which can replace your content in search results.

How to Use Status Codes Well

Design Them With the API

Write down which codes each endpoint returns before writing the handlers, and keep one error body format, RFC 9457 problem details, across every service. Test the error paths as carefully as the happy path: API and integration tests that assert the exact status code catch most of the mistakes above before they ship.

Then Watch Them in Production

Graph 4xx and 5xx rates separately and per endpoint: a jump in 5xx after a release is the clearest signal that the deployment broke something, and a good reason for a rollback. A jump in 4xx often means a client release or a documentation problem instead. Debug individual failures with the curl commands above and the browser tools described in the guide to debugging JavaScript.


How This HTTP Status Codes Cheat Sheet Is Maintained

This page is written and maintained by Bogdan Sandu for TMS Outsource, a software development company. It was checked on October 14, 2026 against RFC 9110, the IANA HTTP Status Code Registry, RFC 9457 and RFC 10008, and the vendor documentation of nginx, Cloudflare and AWS. The output of the examples was produced by running them with curl 8.19, Node.js 24 and Python 3.14. If a snippet is wrong or out of date, the date in the byline shows when it was last reviewed.


HTTP Status Code Questions People Actually Ask

The questions developers search for most about HTTP status codes, answered in a few sentences each.

What are HTTP status codes?

HTTP status codes are three-digit numbers a server sends at the start of every response to say how the request went. The first digit is the class: 1xx informational, 2xx success, 3xx redirection, 4xx client error and 5xx server error. RFC 9110 defines the core codes and IANA keeps the official registry.

What is the difference between 401 and 403?

401 Unauthorized means the request is not authenticated: credentials are missing, wrong or expired, and the response must include a WWW-Authenticate header. 403 Forbidden means the server knows who you are and still refuses; logging in again will not help. A server can also answer 404 to hide that a resource exists.

Should I use 400 or 422 for validation errors?

Use 400 Bad Request when the request cannot be parsed, such as broken JSON or a missing required header, and 422 Unprocessable Content when it parses but the values fail validation or business rules. Both are valid HTTP; pick one convention for your API and document it.

What is the difference between 301, 302, 307 and 308?

301 and 308 are permanent redirects, 302 and 307 temporary ones. With 301 and 302 clients may change a POST into a GET when following the redirect; 307 and 308 require the same method and body. For pages, 301 and 302 are fine; for APIs that receive POST or PUT, use 308 or 307.

What does 429 Too Many Requests mean?

429 means the client sent too many requests in a given time and hit a rate limit. The response should include Retry-After with the number of seconds to wait or a date. Clients should wait at least that long and then retry with exponential backoff and jitter.

What causes 502 Bad Gateway and 504 Gateway Timeout?

Both come from a proxy, load balancer or CDN in front of the application. 502 means the upstream server refused the connection, closed it early or sent an invalid response, often because the app is down or listens on another port. 504 means the upstream did not answer in time, usually a slow request or a proxy timeout shorter than the work.

Is 418 I'm a teapot a real status code?

418 comes from RFC 2324, an April Fools' joke from 1998 about coffee pots. It was never a real HTTP status code, but so much software implements it that RFC 9110 marks it as unused and reserved so that nobody assigns 418 to something else.

Which status code should an API return after creating a resource?

201 Created, with a Location header pointing to the new resource and usually the created resource in the body. If the work is queued and not finished yet, return 202 Accepted with a URL the client can poll.

Is this HTTP status codes cheat sheet up to date?

Yes. It was checked on October 14, 2026 against RFC 9110, the IANA HTTP Status Code Registry, RFC 9457 for problem details and RFC 10008, which added the QUERY method in June 2026. The command and code examples that show output were run with curl 8.19, Node.js 24 and Python 3.14.

Can I download this HTTP status codes cheat sheet as a PDF?

Yes. The page has a print stylesheet, so your browser's print command with Save as PDF produces a clean copy with every output and explanation expanded and the navigation left out. The print core button prints only the core snippets for a short reference.