Status Codes
When you send someone a letter, you eventually learn what happened. The letter arrived and they wrote back. The letter was returned because they moved. The address did not exist. The post office was closed. In every case, the outcome falls into one of a small number of categories, and you adjust your next step accordingly.
HTTP works the same way. Every response begins with a three-digit number—the status code--that tells the client exactly what happened. Did the request succeed? Did the resource move somewhere else? Did the client make a mistake? Did the server break? The status code answers all of these questions before the client reads a single byte of the body. It is the first thing a program checks, and often the only thing it needs.
The Status Line
The status code lives in the first line of every response, called the status line:
HTTP/1.1 200 OK
Three fields, separated by spaces:
-
HTTP/1.1 — the protocol version.
-
200 — the status code, a three-digit integer.
-
OK — the reason phrase, a short human-readable description.
Software makes decisions based on the numeric code alone. The
reason phrase is entirely cosmetic. You could replace OK with
Everything Is Fine or even leave it blank, and the protocol
would still work. The phrase exists so that a human reading raw
traffic with a packet sniffer or terminal can understand what
happened at a glance.
The Five Classes
Status codes are not random. The first digit divides all possible codes into five classes, each representing a fundamentally different kind of outcome:
| Range | Class | Meaning |
|---|---|---|
1xx |
Informational |
The request was received and the server is continuing to process it. |
2xx |
Success |
The request was received, understood, and accepted. |
3xx |
Redirection |
The client must take additional action to complete the request. |
4xx |
Client Error |
The request contains an error and cannot be fulfilled. |
5xx |
Server Error |
The server failed to fulfill a valid request. |
This grouping is the single most useful thing to memorize about
status codes. When you encounter an unfamiliar code, its first digit
immediately tells you whose problem it is: a 4xx means the client
did something wrong; a 5xx means the server did. A 3xx means nobody
did anything wrong—the resource just is not here anymore. Even
software that does not recognize a specific code can fall back to
the class: an unknown 237 is treated as a generic success, and an
unknown 569 as a generic server error.
1xx: Informational
Informational codes are provisional. The server is acknowledging the request but has not finished processing it yet. These responses never carry a body.
100 Continue. This is the most important 1xx code. When a client
wants to send a large body—say, a 500 MB file upload—it can ask
the server for permission first by including an Expect:
100-continue header. The server replies with 100 Continue if it
is willing to accept the body, or with an error code (such as 413
Content Too Large) if it is not. This handshake avoids wasting
bandwidth sending a massive payload that the server would reject:
PUT /uploads/backup.tar.gz HTTP/1.1
Host: storage.example.com
Content-Length: 524288000
Expect: 100-continue
HTTP/1.1 100 Continue
Only after receiving the 100 does the client begin transmitting
the body.
101 Switching Protocols. The server is switching to a different
protocol at the client’s request. This is how HTTP connections
upgrade to WebSocket: the client sends an Upgrade: websocket
header, and if the server agrees, it responds with 101 and the
connection transitions from HTTP to the WebSocket protocol.
103 Early Hints. A relatively recent addition (RFC 8297). The
server sends preliminary Link headers so the browser can start
preloading stylesheets or fonts while the server is still computing
the final response. When the real response arrives, the browser has
already fetched several critical resources.
2xx: Success
These are the codes every client hopes to see. The request worked.
200 OK. The universal success code. For a GET, it means the requested resource is in the body. For a POST, it means the action completed and the body contains the result. It is by far the most common status code on the Web:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 612
<html>
<head><title>Welcome</title></head>
<body>...</body>
</html>
201 Created. The request succeeded and a new resource was
created as a result. Servers typically return this after a POST that
creates a record. The response includes a Location header
pointing to the newly created resource:
HTTP/1.1 201 Created
Location: /api/users/4281
Content-Type: application/json
{"id":4281,"name":"Alice"}
204 No Content. The request succeeded, but there is nothing to send back. Common after a DELETE that removes a resource, or a PUT that updates one when the client does not need a copy of the result. The response has headers but no body:
HTTP/1.1 204 No Content
206 Partial Content. The server is delivering only part of the
resource because the client asked for a range. This is the mechanism
behind resumable downloads and video streaming. When you pause a
download and resume it later, the client sends a Range header
asking for bytes from where it left off, and the server replies
with 206:
GET /videos/lecture.mp4 HTTP/1.1
Host: cdn.example.com
Range: bytes=1048576-
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-5242879/5242880
Content-Length: 4194304
Content-Type: video/mp4
...(bytes 1048576 through 5242879)...
3xx: Redirection
Redirection codes tell the client that the resource it asked for is available, but somewhere else—or that the client already has a valid copy. The client must take one more step to get what it wants.
301 Moved Permanently. The resource has a new, permanent URL.
The server includes the new address in a Location header, and all
future requests should use it. Search engines update their indexes
when they see a 301. This is the code a website sends when it
changes its domain name:
HTTP/1.1 301 Moved Permanently
Location: https://www.new-example.com/about
302 Found. The resource is temporarily at a different URL. The
client should follow the Location header for this request but
continue using the original URL for future requests. Login pages use
this constantly—after you submit your credentials, the server
redirects you to the page you originally wanted:
HTTP/1.1 302 Found
Location: /dashboard
303 See Other. Tells the client to retrieve the result at a different URL using GET, regardless of what method the original request used. This is the standard response after a POST that creates or processes something: the server says "the operation worked, now GET the result here." This pattern prevents the double-submit problem—if the user refreshes the page, the browser re-issues a GET rather than re-posting the form.
307 Temporary Redirect. Like 302, but with an important guarantee: the client must use the same method. If the original request was a POST, the redirect must also be a POST. HTTP/1.1 introduced 307 to resolve an ambiguity in 302, where browsers historically changed POST to GET during redirects.
308 Permanent Redirect. Like 301, but with the same method-preservation guarantee as 307. A POST stays a POST. Use 308 when you permanently move an API endpoint that receives POST requests.
304 Not Modified. This code has nothing to do with location
changes. It is the server’s way of saying "you already have the
latest version." When a client sends a conditional request with an
If-Modified-Since or If-None-Match header, the server can
respond with 304 instead of transmitting the entire resource again.
No body is sent—the client uses its cached copy:
GET /style.css HTTP/1.1
Host: www.example.com
If-None-Match: "abc123"
HTTP/1.1 304 Not Modified
ETag: "abc123"
Cache-Control: max-age=3600
The 304 saves bandwidth and time. On a busy site, the majority of requests for static assets are answered this way.
4xx: Client Error
The 4xx family means the client did something wrong. The request was malformed, unauthorized, or asked for something that does not exist. The server understood the request well enough to know it cannot be fulfilled.
400 Bad Request. The catch-all for malformed requests. The
server could not parse the request because of invalid syntax, a
missing required field, or a body that does not match the declared
Content-Type. When an API returns 400, it usually includes a body
explaining exactly what was wrong:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error":"'email' field is required"}
401 Unauthorized. Despite its name, this code means
unauthenticated. The client has not provided credentials, or the
credentials it provided are invalid. The response includes a
WWW-Authenticate header telling the client how to authenticate:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
403 Forbidden. The server knows who the client is but refuses to grant access. The difference from 401 is important: 401 means "I don’t know who you are—please log in," while 403 means "I know who you are, and you are not allowed." Re-authenticating will not help.
404 Not Found. The most famous status code in the world. The server cannot find the resource at the requested URL. Every web user has encountered a 404 page. In API design, servers sometimes return 404 instead of 403 to hide the existence of a resource from unauthorized clients—if you are not supposed to know it exists, the server tells you it does not exist.
405 Method Not Allowed. The URL is valid, but the method is
not supported for it. A resource might accept GET and POST but not
DELETE. The server must include an Allow header listing the
methods it does support:
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST, HEAD
408 Request Timeout. The client took too long to finish sending its request. Servers set a timeout and, if the client has not completed the request within it, respond with 408 and close the connection.
409 Conflict. The request conflicts with the current state of the resource. A common example is trying to create a user account with a username that already exists, or uploading a file that would overwrite a newer version.
429 Too Many Requests. The client has been rate-limited. It sent
too many requests in a given period of time. The server often
includes a Retry-After header indicating how long the client
should wait before trying again:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{"error":"Rate limit exceeded. Try again in 30 seconds."}
5xx: Server Error
The 5xx codes mean the server knows the request was valid but something went wrong on its side. The fault lies with the server, not the client.
500 Internal Server Error. The generic server-side failure. An unhandled exception, a null pointer, a failed database query-- anything that the server did not anticipate. It is the server equivalent of a shrug. When you see 500 in production logs, it means code needs to be fixed:
HTTP/1.1 500 Internal Server Error
Content-Type: text/html
<html><body><h1>Something went wrong.</h1></body></html>
502 Bad Gateway. A server acting as a gateway or proxy received an invalid response from the upstream server it was forwarding to. If your application sits behind a reverse proxy like Nginx, a 502 usually means your application process crashed or is not listening.
503 Service Unavailable. The server is temporarily unable to
handle requests—usually because it is overloaded or undergoing
maintenance. Unlike 500, this code implies the problem is transient
and the client should try again later. A Retry-After header may
indicate when the server expects to recover:
HTTP/1.1 503 Service Unavailable
Retry-After: 120
504 Gateway Timeout. Like 502, but specifically about time. The proxy or gateway did not receive a response from the upstream server within the allowed time. This is the timeout version of 502.
The Redirection Maze: 301 vs. 302 vs. 303 vs. 307 vs. 308
The redirect codes deserve a closer look because their history is tangled and the distinctions matter in practice.
The original HTTP/1.0 specification defined 301 (permanent) and 302 (temporary). The intention was that clients should preserve the original HTTP method when following a redirect—if you POST to a URL and get a 302 back, you should POST to the new URL too. Browsers, however, ignored this and changed POST to GET when following 302 redirects.
HTTP/1.1 cleaned up the mess by adding three new codes:
| Code | Type | Method preserved? |
|---|---|---|
301 |
Permanent |
No — browsers may change POST to GET |
302 |
Temporary |
No — browsers may change POST to GET |
303 |
Temporary (see other) |
Always changes to GET (by design) |
307 |
Temporary |
Yes — method must not change |
308 |
Permanent |
Yes — method must not change |
The practical rule: use 301 or 308 for permanent moves, 302 or 307 for temporary ones. If you specifically want the client to switch to GET after a POST (the "post/redirect/get" pattern), use 303. If you need the client to keep the same method, use 307 or 308.
Reading a Status Code in Context
A status code never appears alone. It arrives alongside headers and sometimes a body that together tell the full story. Here is a complete exchange where the client requests a page that has moved:
GET /old-page HTTP/1.1 (1)
Host: www.example.com
HTTP/1.1 301 Moved Permanently (2)
Location: /new-page (3)
Content-Length: 0
GET /new-page HTTP/1.1 (4)
Host: www.example.com
HTTP/1.1 200 OK (5)
Content-Type: text/html
Content-Length: 94
<html>
<head><title>New Page</title></head>
<body><p>You found the new page.</p></body>
</html>
| 1 | The client requests the original URL |
| 2 | The server says the resource has permanently moved |
| 3 | The Location header provides the new URL |
| 4 | The client automatically follows the redirect |
| 5 | The resource is delivered from its new home |
Browsers perform this redirect chain automatically and invisibly. The user sees only the final page and the new URL in the address bar. Programs and libraries do the same, though most limit the number of redirects they will follow (typically 20) to avoid infinite loops.
Codes You Will See Every Day
Out of the dozens of defined status codes, a handful appear in the vast majority of real-world traffic:
| Code | When you will see it |
|---|---|
|
Almost every successful page load, API call, and resource fetch. |
|
After creating a resource via POST in a REST API. |
|
After a successful DELETE, or a PUT that needs no response body. |
|
When a website changes its URL structure or migrates to a new domain. |
|
When the browser checks its cache and the resource has not changed. |
|
When an API request has missing or invalid parameters. |
|
When you forget to include your authentication token. |
|
When you are authenticated but lack permission. |
|
When the URL does not match any resource on the server. |
|
When the server has an unhandled bug. |
|
When the reverse proxy cannot reach the application server. |
|
When the server is overloaded or down for maintenance. |
Memorizing these twelve codes covers the overwhelming majority of HTTP traffic. The rest are important in specific contexts—range requests, content negotiation, WebDAV—but these are the ones that appear in logs, error pages, and debugging sessions day after day.
Why the First Digit Matters
The five-class design is not just a convenience for humans. The HTTP
specification (RFC 9110) requires that software treat an unrecognized
status code as equivalent to the x00 code of its class. If a client
receives a response with status code 299, and it does not know what
299 means, it must treat it as 200. If it receives 599, it must
treat it as 500.
This rule makes the protocol future-proof. New status codes can be defined without breaking existing clients. As long as the first digit is correct, old software will do something reasonable—it just will not do anything specific to the new code. This is one of the small design decisions that allowed HTTP to evolve for over three decades without fragmenting the Web.