developer · · Updated

HTTP Status Codes You Will Actually See, From One Real Site

By

Lists of HTTP status codes are easy to find. What's harder to picture is which ones a real site actually sends, and why. On 26 September 2026 this site, plus the two outside services it talks to, were probed with curl, and between them they returned eight different status codes. Each one below is a real response, and each teaches something a list doesn't.

What came back

RequestStatusWhat it means here
http://devbiztoolshub.com/about301 Moved Permanently → https://www.devbiztoolshub.com/aboutOld protocol and the bare domain both redirect, in one hop
https://www.devbiztoolshub.com/about/308 Permanent Redirect → /aboutThe trailing-slash form redirects to the canonical URL
https://www.devbiztoolshub.com/about200 OKThe page itself
https://www.devbiztoolshub.com/does-not-exist404 Not FoundA real 404, with an error page as the body
POST https://www.devbiztoolshub.com/about405 Method Not AllowedA static page can't accept a form post
A hashed JavaScript file, sent with If-None-Match304 Not ModifiedThe browser's cached copy is still current
POST /api/track (the page-view counter)204 No ContentCounted; nothing to send back
GET /api/track404 Not FoundOnly POST is handled; a GET falls through to the 404 page

Two outside services added three more:

RequestStatus
https://api.frankfurter.app/latest?from=USD&to=INR (exchange rates, old address)301 → https://api.frankfurter.dev/v1/latest?...
Formspree contact form, posted empty400 Bad Request, body {"error":"Can't send an empty form"}
Formspree, posted to a form ID that doesn't exist404 Not Found, body {"error":"Form not found"}

301 and 308: both permanent, not identical

Both tell browsers and search engines that the resource has a new permanent address, and Google treats them the same way for indexing. The difference is the request method. With a 301, clients are allowed to turn a POST into a GET when they follow the redirect, and browsers do. With a 308, the method and body must be repeated unchanged (RFC 9110). For page URLs that only ever receive GETs, either works. For an API endpoint that receives form posts, a 301 can silently turn a submission into an empty GET.

Notice the 308's Location header was just /about, a path rather than a full URL. That's allowed: clients resolve it against the URL they requested.

When a redirect breaks the request: CORS

The exchange-rate service used to live at api.frankfurter.app. That address now answers with a 301 to api.frankfurter.dev, but the redirect response carries no Access-Control-Allow-Origin header. A browser making a cross-origin fetch checks CORS on every response in the chain, including redirects. So the request failed in the browser with a CORS error, while the same URL worked perfectly from curl, which doesn't enforce CORS. This site's currency converter fell back to an old rate for exactly this reason until it was pointed at the new address. When an API call fails in the browser but not in curl, check the redirect chain for missing CORS headers.

304: the fastest response is no body

The JavaScript file came back with an ETag, an identifier for that exact version of the file. Sending the ETag back in If-None-Match got a 304 and no body: the server confirmed the browser's copy was current without resending it. That file also had Cache-Control: public, max-age=31536000, immutable: its name contains a hash of its contents, so a new version always gets a new name, and the old one can be cached for a year. The HTML pages have no ETag and a one-hour max-age, so a browser checks for a new version at most an hour later.

204 and the empty-body trap

A 204 means success with nothing to return. Calling response.json() on it throws Unexpected end of JSON input in Chrome, because an empty body isn't JSON. Check the status before parsing. The common JSON errors guide lists what each browser says for this and other parse failures.

400 vs 404 from an API

Formspree used both codes precisely. An empty submission to a real form is the client's fault in a way the server can describe (400, "Can't send an empty form"); a submission to a form ID that doesn't exist is 404. Both came with a JSON body naming the problem, which is what makes an API pleasant to debug. A bare status code tells you less.

405 without an Allow header

The POST to a page got 405, the right code. RFC 9110 says a 405 must include an Allow header listing the methods that would work, but this one didn't; Cloudflare's static hosting leaves it out. The counter endpoint shows the other common behaviour: a GET to a URL that only handles POST came back as 404, not 405. Many frameworks answer that way, so a 404 on an API call can mean "wrong method" as well as "wrong URL".

404 is only honest if it is a 404

A made-up URL on this site returns a real 404 status with an error page as the body. A site that shows its error page with a 200 status (a "soft 404") tells search engines the page exists; Google then has to guess from the content, reports such URLs as soft 404s in Search Console, and leaves them out of results. curl -I on a nonsense URL shows the true status in a second.

To look up a code you haven't met yet, the HTTP Status Code Reference lists the common ones by class, with a search box.

Related guides