Nine HTTP status codes, and the sixty you can forget

30/07/2026 · Laurent HTTP status codes
Nine HTTP status codes, and the sixty you can forget

There are sixty-three registered HTTP status codes. Every reference page lists all of them, in numerical order, with a one-line definition taken from the specification. That format is fine for an implementer writing a client library. It is useless at eleven at night when a client says their site is broken.

What you want at that hour is not a definition. It is an elimination: given this number, which machines are working, and where do I not have to look.

The nine

CodeWhat it proves is workingWhere to look
200DNS, network, TLS, web server, application. All of it.The body. This is the code that lies most often.
301 / 302Everything, plus a rule that fired.The target. Chains and loops live here.
401Everything. The server understood and wants credentials.Your request, not the server.
403Everything. The server understood and refuses.Permissions, or a firewall pretending to be your app.
404Everything up to routing.The URL, or a routing rule that changed.
429Everything. You are being counted.Who is counting, and whether the count is yours.
500Everything up to and including your code running.The application log. This is the only code that means "your bug".
502DNS, network, TLS, and the proxy.Upstream. The process behind the proxy.
503Everything. Something refused on purpose, or is full.Whether it was on purpose.
504DNS, network, TLS, proxy, and the upstream is alive.A timeout value, and what is slow.

That is ten rows for nine codes, because 301 and 302 are the same problem with different caching. The rest of the sixty-three are either things you will never emit, things a client library handles for you, or April Fools jokes that made it into a registry.

The split that actually matters

Forget the four-hundreds and five-hundreds framing for a minute. Sort them by who is at fault, and something more useful appears.

Your code ran and failed: 500. That is the whole list. One code out of sixty-three means "there is a bug in the thing you deployed", and it is the one that comes with a stack trace in a log you own.

Your code never ran: 502, 503, 504. Something in front of your application decided not to, or could not, hand it the request. Reading your application log for these is a waste of an evening, and it is what most people do first.

Your code ran and did exactly what it should: 401, 403, 404, 429, 301. These are not errors. They are the server working. A monitoring alert on a 404 is an alert on the correct behaviour of a correctly configured site.

Your code ran, succeeded, and the page is still broken: 200. Which brings us to the interesting part.

Why 200 is the code that costs the most

Every failure above announces itself. Somebody wrote an error page, a log line got emitted, a monitor turned red. The failures that hurt are the ones that answer 200.

A fatal error printed into a page whose headers already went out: 200. A stylesheet that returns 404 while the HTML returns 200, so the page is text on white: 200 on the URL you are watching. A staging noindex shipped to production: 200, and the site quietly leaves the index over six weeks. A database error rendered as body text by a theme's error handler: 200, with the connection string sometimes visible in it.

None of those change the number. Every uptime checker built on status codes alone reports a perfect month. And every one of them is something a person notices before your tooling does, which is the exact failure mode this whole trade exists to prevent.

There is a cheap partial defence, and it takes one line. Check for something that should be in the page as well as for the code:

curl -s https://example.com/ | grep -q 'id="main-nav"' || echo 'page renders without its nav'

A string that only appears when the page rendered fully is a better health signal than the status line, because it fails for reasons the status line cannot express. Most checkers support this and almost nobody configures it.

Reading the code and the headers together

The status line alone is rarely the whole answer. Three headers change what a code means.

Server and Via tell you which machine wrote the response. On a 502, that is the difference between "your PHP died" and "Cloudflare could not reach you", which are different phone calls to different companies.

Retry-After on a 503 is the difference between a planned maintenance window and an incident. It is the one header that resolves the ambiguity, and most maintenance pages omit it.

Cache-Control on an error decides how long the error outlives its cause. A 404 cached for a day by a CDN is a 404 that survives the fix by a day.

All three come from one command:

curl -sSI https://example.com/

What to alert on

If you watch sites for other people, the useful policy is shorter than most teams' current configuration.

Alert on 500, 502 and 504, confirmed by a second check from somewhere else. Alert on 503 without Retry-After. Alert on 200 where the expected content is missing. Record everything else, including the 404s and the 429s, and look at the counts weekly rather than at three in the morning.

The reason to record the ones you do not alert on is that they are the early warning. A pool filling up shows as a slow rise in intermittent 502s weeks before it shows as an outage, which is the point of the 502 article, and a creeping response time shows as 504s at the edge before anyone complains, which is the point of the 504 one. A tool that only stores confirmed outages has thrown that away before you could look at it.

If you want to see what a check looks like when it reads the body as well as the number, the free checker here does that on any address.

Share this post.
Stay up-to-date

Subscribe to our newsletter

Don't miss this

You might also like