Everyone knows 404 and 500. The codes that actually cause bugs and security holes are the ones people reach for without checking: 401 vs 403, 302 vs 307, and the criminally underused 429.
HTTP status codes are a shared vocabulary between every client and every server on the web, and most developers speak only a few words of it fluently. That's usually fine — until the day a misused code causes a login form to leak which usernames exist, or a redirect quietly turns a POST into a GET and drops the request body. The codes that cause trouble aren't the obscure ones. They're the common ones people reach for by reflex without checking what they actually mean.
The five classes
Every status code is three digits, and the first digit tells you the category. RFC 9110, the current HTTP specification, states it exactly:
"The first digit of the status code defines the class of response. The last two digits do not have any categorization role."
— IETF RFC 9110, "HTTP Semantics"
So: 1xx informational, 2xx success, 3xx redirection, 4xx the client's fault, 5xx the server's fault. Get the first digit right and you've communicated the most important thing. The specific bugs live in choosing the wrong second and third digits within a class.
401 vs 403: the one that leaks information
These two feel interchangeable and aren't. 401 Unauthorized means "I don't know who you are — authenticate." 403 Forbidden means "I know who you are, and you're not allowed." Using them carelessly creates a real security problem: if a resource returns 403 when it exists but you lack access, and 404 when it doesn't exist, an attacker can map which resources are real by watching the difference. Many security-conscious APIs deliberately return 404 for "forbidden" precisely to avoid confirming a resource exists. The distinction isn't pedantry — it's an information-disclosure decision.
302 vs 307: the redirect that mutates your request
Here's a bug that's hard to trace. When you redirect a POST request with a 302 Found, many clients will follow it but switch the method to GET — dropping your request body along the way. That's a historical quirk, not what most people intend. If you want a redirect that preserves the method and body, you need 307 Temporary Redirect (or 308 for permanent). The symptom is maddening: form submissions that "work" but arrive empty, or an API call that silently becomes a GET after a redirect. When a redirected POST loses its data, the status code is almost always why.
429: the code nobody sends enough
429 Too Many Requests is how a server politely says "slow down," ideally with a Retry-After header telling the client when to try again. It's underused — plenty of services just start failing with 500s or dropping connections under load instead of returning a clear 429. That's a missed opportunity: a well-behaved client will back off and retry on a 429, but it can't do the right thing if you never tell it to slow down. If you run an API, rate-limiting with 429 and Retry-After is one of the cheapest reliability improvements available.
The 200-for-an-error anti-pattern
The worst status-code habit is returning 200 OK with an error described in the body — {"success": false, "error": "..."} and an HTTP 200. This breaks every layer that relies on the status code: monitoring thinks everything's healthy, caches store the "error" as a valid response, and client libraries that check the status see success. The status code is the machine-readable truth about whether the request succeeded; the body is for humans and details. An error is a 4xx or 5xx, full stop — encoding failure inside a 200 is a bug that hides other bugs.
Look it up, don't guess
When you're not certain which code fits, check rather than reach for a familiar one. An HTTP status codes reference gives you the precise meaning and class in a second, which is faster than shipping a 302 where you needed a 307. When you're debugging a redirect chain, a URL parser helps you inspect exactly where a Location header is pointing. And since so many API responses carry their real detail in a JSON body alongside the status, a JSON formatter makes that payload readable while you confirm the code and the body actually agree. The status line is a contract — the more precisely you use it, the fewer mysteries you ship.