HTTP Status Codes Explained: The Ones You Actually See

The first digit of an HTTP status code tells you who to look at: 2xx means it worked, 3xx means go somewhere else, 4xx means the request was wrong, and 5xx means the server failed. Within those, the codes people actually run into are 200, 201, 204, 301, 302, 304, 400, 401, 403, 404, 405, 409, 422, 429, 500, 502, 503 and 504, each explained below with what to do about it.

The definitions come from RFC 9110, the current HTTP semantics standard, except 429, which is in RFC 6585.

How to see the status code

In a browser, open developer tools (F12), go to the Network tab and reload; the Status column shows the code for every request. From a terminal, curl -I prints just the response headers:

curl -I http://github.com

Example output:

HTTP/1.1 301 Moved Permanently
Content-Length: 0
Location: https://github.com/

To print only the number, which is handy in scripts: curl -s -o /dev/null -w "%{http_code}\n" https://example.com/.

The five classes

ClassMeaning (RFC 9110)Whose problem
1xx InformationalRequest received, continuing processNobody; you rarely see these
2xx SuccessfulThe request was successfully received, understood, and acceptedNobody
3xx RedirectionFurther action needs to be taken to complete the requestUsually handled automatically
4xx Client ErrorThe request contains bad syntax or cannot be fulfilledThe caller: fix the request
5xx Server ErrorThe server failed to fulfil an apparently valid requestThe server or something in front of it

2xx: success

  • 200 OK: "the request has succeeded". What the body contains depends on the method: the resource for a GET, the result of the action for a POST.
  • 201 Created: the request created a new resource. The server identifies it with a Location header, so a client that just POSTed a new order should read that header to find the order's URL.
  • 204 No Content: success, with nothing to send back. Common for DELETE and for PUT updates. A 204 response has no body, so client code that always calls response.json() will throw on it.

3xx: redirects and caching

  • 301 Moved Permanently: the resource has a new permanent URL, given in Location. Use it when you move a page for good (HTTP to HTTPS, a renamed URL).
  • 302 Found: the resource is temporarily somewhere else; keep using the original URL in future. RFC 9110 notes that "for historical reasons" a client may change a POST into a GET when following a 301 or 302, and MDN says browsers following the Fetch standard do this for 302. If the method must be kept, use 307 (temporary) or 308 (permanent), which forbid changing it.
  • 304 Not Modified: not an error. The client asked "has this changed since my cached copy?" (with If-None-Match or If-Modified-Since), and the answer is no, so the server sends no body and the client uses its cache. Seeing many 304s in the Network tab means caching is working.

4xx: the request is the problem

CodeMeaningWhat to check
400 Bad RequestThe server can't process the request because of a client error, such as malformed syntaxInvalid JSON, a wrong Content-Type, missing required fields, a malformed query string
401 UnauthorizedNo valid authentication credentialsMissing or expired token, wrong API key, Authorization header not sent
403 ForbiddenUnderstood, but refusedYour account or token lacks permission for this resource or action
404 Not FoundNo current representation found, or the server won't say it existsTypo in the path, wrong base URL or API version, trailing slash differences
405 Method Not AllowedThe URL exists but doesn't support this methodSending POST where only GET works; the Allow header lists what is supported
409 ConflictConflicts with the current state of the resourceDuplicate unique value, editing a stale version; re-fetch and retry
422 Unprocessable ContentSyntax is fine, but the content can't be processedField validation failures in well-formed JSON
429 Too Many RequestsToo many requests in a given time (rate limiting)Slow down; honour the Retry-After header if present

Two details from the RFC that save debugging time. A server that sends 405 "MUST generate an Allow header" listing the methods the URL supports, so read it. And a server that wants to hide that a forbidden resource exists may send 404 instead of 403, so a 404 on a URL you know exists can be a permissions problem.

401 vs 403

The names are misleading. 401 is called "Unauthorized" but means unauthenticated, as MDN puts it: the server doesn't know who you are. RFC 9110 requires a 401 to include a WWW-Authenticate header describing how to authenticate. You can trigger a 401 by calling GitHub's API without a token:

curl -I https://api.github.com/user
HTTP/1.1 401 Unauthorized

When this was checked for this article, GitHub's response carried no WWW-Authenticate header at all, so don't rely on every API following that part of the standard; read the API's own documentation for how to authenticate.

403 means the server knows the request and refuses it. If credentials were sent, the RFC says the server "considers them insufficient to grant access", and the client should not retry with the same ones. The practical test: would logging in, or refreshing the token, fix it? Then it is a 401. Would it only be fixed by someone granting you more permissions? Then it is a 403.

5xx: the server is the problem

  • 500 Internal Server Error: "an unexpected condition" stopped the server. This is the generic crash code, usually an unhandled exception. The useful information is in the server's logs, not in the response; how to read a stack trace covers what to look for there.
  • 502 Bad Gateway: a proxy or gateway got "an invalid response" from the server behind it.
  • 503 Service Unavailable: temporary overload or maintenance. The server may send Retry-After to say when to try again.
  • 504 Gateway Timeout: a proxy or gateway "did not receive a timely response" from the server behind it.

502 vs 504

Both mean there are at least two servers involved: something in front (a reverse proxy such as nginx, a load balancer, a CDN, an API gateway) and the application behind it. The front server is the one reporting the error, about the one behind it.

  • 502: the application answered, but with something the proxy couldn't use. MDN's distinction is that if the proxy received no HTTP response at all from the origin, it sends a 504 instead.
  • 504: the application didn't answer within the proxy's time limit. Look for slow database queries, a hung process, or a request that does too much work synchronously.

In both cases, check the application's own logs first, then the proxy's error log; the proxy usually records why it gave up on the upstream.

Which codes to retry

Retrying a 4xx unchanged gets the same answer, with one exception: 429, after waiting. Among server errors, 503 is defined as temporary and may come with Retry-After; 502 and 504 can also clear once the upstream recovers. Leave an increasing delay between attempts rather than retrying in a tight loop. Before retrying a POST automatically, make sure repeating it can't create a duplicate (a second order, a second payment).

If a browser request fails with no status code at all and the console mentions CORS, the server may well have replied with 200; the browser just won't show it to your script. See CORS errors explained.

Frequently asked questions

What is the difference between 401 and 403?
401 means the server doesn't know who you are: the request had no valid credentials, and the server must send a WWW-Authenticate header saying how to authenticate. 403 means the server understood the request and refuses it; if you sent credentials, they are not sufficient. Logging in can fix a 401; it won't fix a 403 unless you log in as someone with more permissions.
What is the difference between 502 and 504?
Both come from a proxy, load balancer or gateway in front of the real server. 502 Bad Gateway means it received an invalid response from the server behind it. 504 Gateway Timeout means it received no response in time. A 504 points at something slow or hung; a 502 points at something that answered badly.
Should an API return 400 or 422 for validation errors?
RFC 9110 defines 400 for requests the server can't process because of a client error such as malformed syntax, and 422 for content whose syntax is correct but whose instructions can't be processed. Many APIs use 422 for well-formed JSON that fails validation and 400 for JSON that doesn't parse. Either is acceptable if the API is consistent and documents it.
Is 404 always permanent?
No. RFC 9110 says a 404 doesn't indicate whether the missing resource is gone temporarily or permanently. A server that knows a resource is permanently removed should send 410 Gone instead.

Sources