HTTP Status Codes Cheat Sheet: Every Code Explained (1xx-5xx)
You open DevTools and the Network tab is half red. Your endpoint returns 502 in production and 200 locally, and a coworker on Slack just asked “should this be a 401 or a 403?”. HTTP status codes look simple (three digits, five buckets), but the wrong choice leaks information, breaks SEO, and makes on-call rotations miserable.
This guide is a complete HTTP status codes cheat sheet for working developers. You get three things: a quick-reference table of every code you actually see in the wild, decision matrices for the pairs people get wrong (301 vs 302, 401 vs 403, 404 vs 410, 502 vs 504), and a tooling section that shows how to inspect status codes from curl, fetch, and Python requests. Every code below is grounded in RFC 9110, the current HTTP semantics standard, and the IANA HTTP Status Code Registry.
Quick reference: all HTTP status codes at a glance
These are the codes you’ll meet in production, grouped by class.
| Code | Name | When you’ll see it |
|---|---|---|
| 100 | Continue | Sending a large POST body with Expect: 100-continue |
| 101 | Switching Protocols | WebSocket handshake, HTTP/2 upgrade |
| 103 | Early Hints | Server pushes Link headers before the real response |
| 200 | OK | Default success for GET, PUT, PATCH |
| 201 | Created | POST that creates a resource (returns Location) |
| 202 | Accepted | Async job queued, work not yet done |
| 204 | No Content | DELETE success, PUT with no body to return |
| 206 | Partial Content | Range request, video seek, resumable download |
| 301 | Moved Permanently | Old URL retired, search engines transfer link equity |
| 302 | Found | Temporary redirect, original URL still canonical |
| 303 | See Other | Post/Redirect/Get pattern after a form POST |
| 304 | Not Modified | Conditional GET with matching ETag or If-Modified-Since |
| 307 | Temporary Redirect | Like 302, but method and body are preserved |
| 308 | Permanent Redirect | Like 301, but method and body are preserved |
| 400 | Bad Request | Malformed JSON, missing required field, schema fail |
| 401 | Unauthorized | No credentials or expired token |
| 403 | Forbidden | Authenticated but not allowed |
| 404 | Not Found | Resource doesn’t exist (or you’re hiding it) |
| 405 | Method Not Allowed | POST to a GET-only endpoint (must include Allow) |
| 408 | Request Timeout | Client took too long to send the request |
| 409 | Conflict | Optimistic-lock failure, duplicate key |
| 410 | Gone | Resource permanently deleted, won’t come back |
| 415 | Unsupported Media Type | Wrong Content-Type, e.g. XML to a JSON API |
| 422 | Unprocessable Content | Syntax valid, semantics invalid (validation error) |
| 425 | Too Early | TLS 1.3 early-data replay risk |
| 428 | Precondition Required | Server requires If-Match to prevent lost updates |
| 429 | Too Many Requests | Rate limited (must include Retry-After) |
| 451 | Unavailable for Legal Reasons | DMCA, GDPR takedown, geo-block |
| 500 | Internal Server Error | Unhandled exception in your code |
| 501 | Not Implemented | Method or feature not supported (rare in REST) |
| 502 | Bad Gateway | Upstream returned an invalid response |
| 503 | Service Unavailable | Maintenance mode or overload |
| 504 | Gateway Timeout | Upstream didn’t respond in time |
| 507 | Insufficient Storage | WebDAV ran out of disk |
| 508 | Loop Detected | Infinite redirect or recursion in WebDAV |
| 511 | Network Authentication Required | Captive portal on hotel/airport WiFi |
The following sections unpack each class with decision matrices, anti-patterns, and the SEO consequences of getting it wrong.
How HTTP status codes work (3-digit anatomy)
Why three digits?
HTTP status codes are three decimal digits because HTTP/0.9 needed a fixed-width signal small enough for a parser to branch on quickly and large enough to leave room for new codes. Three digits gives you 900 possible values (100 to 999), which is more than enough; the IANA registry only uses about 60 of them today.
The first digit is the class. The second and third are the specific code inside that class. A client that doesn’t recognize 418 should fall back to handling it as a generic 4xx. RFC 9110 §15 makes this explicit: clients must treat unrecognized codes as the x00 of their class.
The five categories at a glance
| Class | Meaning | Body required? | Cacheable by default? |
|---|---|---|---|
1xx | Informational, interim, more is coming | No | No |
2xx | Success, request was understood and accepted | Often | Depends on method |
3xx | Redirection, further action needed | Optional | 301, 308 yes; 302, 307 no |
4xx | Client error, your fault, fix the request | Yes (explain) | Generally no |
5xx | Server error, our fault, retry might help | Yes (explain) | No |
The “cacheable by default” column matters. CDNs and browsers cache 301 and 308 aggressively and forever, so picking the wrong redirect code in production is hard to undo because users have the redirect cached locally. The SEO section returns to this.
If you want to dig deeper into URL structure (which is what the redirect codes operate on), the URL Encoding & Decoding Guide walks through percent-encoding, query strings, and the byte-level pipeline that determines what makes a URL valid in the first place.
1xx: Informational (when you’ll actually see them)
Most developers go years without seeing a 1xx directly. They are interim responses; the server is telling the client “I’m still here, keep going.”
Browser DevTools usually hides them, and most HTTP libraries collapse them into the final response.
For each code below, MDN’s HTTP response status reference is a useful cross-reference if you want a second pair of eyes on a definition.
100 Continue
The client sends Expect: 100-continue in its headers and waits before transmitting a large request body. The server replies 100 Continue if it’s willing to accept the body, or a 4xx if it’s going to reject the request anyway. This saves bandwidth on big uploads; there’s no point sending 200 MB if the server is going to reject it for a missing header.
curl -v -H "Expect: 100-continue" \
-H "Content-Type: application/octet-stream" \
--data-binary @big-file.bin \
https://api.example.com/upload
If you don’t see < HTTP/1.1 100 Continue in the verbose output, your client probably stripped the header or the server doesn’t support it.
101 Switching Protocols
The handshake that turns an HTTP connection into a WebSocket or HTTP/2 connection. The client sends Upgrade: websocket, the server replies 101 Switching Protocols, and from that point the connection speaks a different protocol. You’ll see this in the Network tab of any chat app, live dashboard, or collaboration tool.
103 Early Hints
A relatively new code (RFC 8297, 2017) that lets the server send Link headers for preload hints before the main response is ready. The browser starts fetching CSS and JS while the server is still rendering. As of 2026, Cloudflare, Fastly, and Vercel all support 103 in production; it’s the modern alternative to HTTP/2 server push (which was deprecated in Chrome).
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Type: text/html
...
Anti-pattern check. If your client never sees 1xx codes when you expect them, the issue is usually a reverse proxy. Older nginx versions strip Expect: 100-continue and 103 Early Hints. Check your proxy config before assuming the server is broken.
2xx: Success (beyond just 200)
Returning 200 OK for everything is the most common code-smell in REST APIs. The 2xx family carries semantic information that makes clients smarter and caches more efficient.
200 OK
The default. A GET returns the resource, a PUT returns the updated resource (or 204), a PATCH returns the patched resource. If you don’t have a reason to use a more specific code, use 200.
201 Created
A POST that creates a new resource should return 201 plus a Location header pointing to the new resource. This is how RESTful clients discover the canonical URL of the thing they just made.
HTTP/1.1 201 Created
Location: /api/users/42
Content-Type: application/json
{"id": 42, "name": "Ada Lovelace"}
202 Accepted
The server accepted the request but hasn’t finished processing. Use this for async work; the client should poll, subscribe to a webhook, or check a status endpoint. Pair it with a job ID in the body.
204 No Content
Success, no body. Common for DELETE (the resource is gone, what would you return?) and for PUT operations where the client already knows the new state. Browsers will not change the current page if a form submission returns 204, which is useful for fire-and-forget actions in single-page apps.
206 Partial Content
Returned for range requests. The client asked for bytes 1000-2000 with a Range: bytes=1000-2000 header, and the server responded with just that slice. Video streaming, resumable downloads, and HTTP-based file syncing all rely on 206.
Decision: 200 vs 201 vs 204 for POST
| Scenario | Code | Body |
|---|---|---|
| POST creates a new resource | 201 Created | New resource (or just ID) + Location |
| POST triggers async work, result not ready | 202 Accepted | Job ID, poll URL |
POST is an action with no resource (e.g. /login) | 200 OK | Action result (token, status) |
| POST succeeds but response is empty | 204 No Content | (none) |
Stuck between 200 and 201? Ask: “did the server create a resource that now has its own URL?” If yes, 201. If no, 200.
3xx: Redirection (301 vs 302 vs 307 vs 308)
Redirects are the most-misused class. The differences between 301, 302, 307, and 308 come down to three independent questions: is the move permanent, is the method preserved, and is the response cacheable.
301 Moved Permanently
The resource has moved and won’t come back. Search engines transfer link equity to the new URL. Browsers and CDNs cache 301 indefinitely, so if you redirect /old to /new with a 301 and then change your mind, users with cached redirects will keep going to /new forever (or until they clear their cache).
Historically, browsers may rewrite the request method on a 301 (POST to GET), which is why HTTP/1.1 introduced 308 to fix that.
302 Found
Temporary redirect. The original URL is still canonical, so search engines should keep indexing the original. Use this for A/B test routing, maintenance pages, or “log in to continue” flows.
Like 301, browsers historically rewrote POST to GET on 302. If you need to redirect a POST and keep it a POST, use 307 instead.
303 See Other
Always rewrites the method to GET. The Post/Redirect/Get pattern: form posts to /submit, server returns 303 with Location: /thank-you, browser does a GET /thank-you. Refreshing the thank-you page doesn’t re-submit the form. This is what 303 was designed for.
304 Not Modified
The conditional response. Client sends If-None-Match: "abc123" (or If-Modified-Since), server checks if the resource changed, and if not returns 304 with no body. The browser uses its cached copy. This is how every CDN and caching layer keeps your site fast.
307 Temporary Redirect
Like 302, but the method must not change. POST stays POST, body is preserved. Use this when you want a temporary redirect on a non-GET request.
308 Permanent Redirect
Like 301, but the method must not change. The modern, safer choice for permanent redirects on APIs that accept POST/PUT.
Decision matrix: which redirect code?
| Permanent (cache forever) | Temporary (don’t cache) | |
|---|---|---|
| Method may change to GET | 301 Moved Permanently | 302 Found |
| Method must stay the same | 308 Permanent Redirect | 307 Temporary Redirect |
Special case: if you specifically want POST to GET (the Post/Redirect/Get pattern), use 303 See Other.
For HTML pages with browser navigation, 301 and 302 are usually fine because GET is GET. For APIs and forms, prefer 308 and 307 to avoid surprising method rewrites.
4xx: Client errors (picking the right one)
4xx means the client did something wrong. The richer your 4xx vocabulary, the easier your API is to use; clients can branch on the code instead of parsing error strings.
400 Bad Request
Generic syntax error. Malformed JSON, missing required field at the structural level, request the server can’t even parse. If the request parses but fails business validation, prefer 422.
401 Unauthorized vs 403 Forbidden
The most-confused pair in HTTP. The split is simple once you see it. A 401 Unauthorized says the request lacks valid authentication; the server doesn’t know who you are, and sending the credentials again (or refreshing the token) might fix it. The response must include a WWW-Authenticate header per RFC 9110 §15.5.2. A 403 Forbidden says the server knows who you are and is refusing anyway. Sending the request again won’t help. You need different credentials or different permissions.
| You see | What’s true |
|---|---|
401 with WWW-Authenticate: Bearer | No token, expired token, or invalid token |
403 after a successful login | Logged in, but this user can’t access this resource |
401 after a successful login | Bug, you probably want 403 |
Anti-pattern: 403-as-404. Some sites return 403 when an unauthenticated user requests /admin/dashboard. This leaks the existence of /admin/dashboard. GitHub solves this by returning 404 for private repositories you’re not a member of, so the resource “doesn’t exist” from your perspective. That’s a deliberate information-hiding choice, not a bug.
404 Not Found vs 410 Gone
Both say “this resource isn’t here.” The difference is permanence and SEO. A 404 Not Found might exist, might not, might come back; search engines will keep checking. A 410 Gone was here, was deliberately removed, and will not return; search engines drop it from the index much faster.
If you delete a product page and want it out of Google’s index now, 410 is the right call. If a URL is just temporarily broken, 404 is fine.
405 Method Not Allowed
The URL exists but doesn’t accept this method. The response must include an Allow header listing the supported methods.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
{"error": "POST is not allowed on this endpoint"}
Forgetting the Allow header is the #1 contract violation in hand-rolled REST APIs.
408 Request Timeout
The client started sending a request and then went silent. The server gave up. Different from 504 Gateway Timeout, which is about the upstream; 408 is “you, the client, took too long.” Servers should send Connection: close to signal cleanup.
409 Conflict
The request conflicts with the current state. Most common use: optimistic locking. The client sends If-Match: "etag-v3" and the server’s current ETag is "etag-v4", so the update is rejected with 409.
410 Gone
See above for permanent deletion. Useful for removing soft-deleted records from search indexes.
415 Unsupported Media Type
The client sent a body the server doesn’t understand. POSTing XML to a JSON-only API gets 415. The response should hint at acceptable types.
422 Unprocessable Content
The request parses fine, but it fails semantic validation. RFC 9110 finally promoted this from WebDAV to the core spec in 2022. Use 422 for validation errors:
{
"error": "validation_failed",
"details": [
{"field": "email", "message": "must be a valid email"},
{"field": "age", "message": "must be at least 13"}
]
}
If your API can’t decide between 400 and 422, the rule of thumb is: 400 for “I can’t even parse this,” 422 for “I parsed it and it doesn’t make sense.”
425 Too Early
Sent when the server doesn’t want to risk processing a request that might be a TLS 1.3 early-data replay. Mostly relevant to CDNs and reverse proxies.
428 Precondition Required
The server insists you send If-Match or If-Unmodified-Since to avoid the lost-update problem. Used in collaborative editing APIs.
429 Too Many Requests
Rate limited. The response must include Retry-After (in seconds, or as an HTTP date) so well-behaved clients can back off.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{"error": "rate_limited", "limit": 100, "window": "1m"}
451 Unavailable for Legal Reasons
The number is a Bradbury reference. The use case isn’t fictional: DMCA takedowns, GDPR right-to-be-forgotten removals, and country-level geo-blocks all justify 451. The response should include a Link header pointing to the legal authority requiring the block, per RFC 7725.
418 I’m a Teapot (the Easter egg)
Yes, it’s real. RFC 2324 (April Fools 1998) and the IETF kept it on the books because too many products joke-implemented it. Don’t ship 418 in a real API; most reverse proxies and load balancers will mishandle it.
Decision matrix: which 4xx?
| Situation | Code |
|---|---|
| Body is malformed or unparseable | 400 |
| No authentication / expired token | 401 |
| Authenticated but not allowed | 403 |
| URL doesn’t exist (or you’re hiding it) | 404 |
| URL existed, deliberately removed | 410 |
| Wrong HTTP method | 405 (with Allow) |
Wrong Content-Type | 415 |
| Optimistic-lock conflict | 409 |
| Validation error (parses, doesn’t validate) | 422 |
| Rate limited | 429 (with Retry-After) |
| Blocked for legal reasons | 451 |
5xx: Server errors (what’s really broken)
5xx is “our fault.” On-call engineers care most about which 5xx woke them up at 3 AM, because the code tells you which layer to investigate first.
500 Internal Server Error
The catch-all. Almost always means an unhandled exception bubbled up to the framework’s default handler. It tells you nothing about the cause; that’s why structured logging matters more than the status code here.
501 Not Implemented
The server doesn’t support the method at all. Different from 405 (this method isn’t allowed for this URL); 501 says “this server has no idea what PROPFIND even means.” Rare in REST APIs.
502 Bad Gateway
A reverse proxy or load balancer received an invalid response from the upstream. The upstream replied, but with garbage: wrong protocol, malformed headers, dropped connection mid-response. If you see 502 from your CDN, the origin is likely crashing or returning truncated bodies.
503 Service Unavailable
The server is intentionally not serving requests right now. Use this for maintenance windows or graceful overload responses. Should include Retry-After. Responses should include a Retry-After header.
504 Gateway Timeout
The reverse proxy waited for the upstream and the upstream never replied in time. The upstream is slow or stuck, which is different from 502, where the upstream replied with garbage.
502 vs 504: the on-call diagnosis
| You see | First thing to check |
|---|---|
502 Bad Gateway | Upstream is replying with invalid data, so check origin logs for crashes, malformed responses, protocol mismatches |
504 Gateway Timeout | Upstream is hanging, so check origin CPU, DB queries, downstream API calls, and the proxy’s proxy_read_timeout |
A common mix-up: a database query that takes 60 seconds will surface as 504 if your proxy times out at 30 seconds, but as 500 if the app server times out at 90 seconds and raises an exception. Same root cause, different code, different log line; train your dashboards to surface both.
507 Insufficient Storage
WebDAV-specific. Disk full on the server. If you see this from a non-WebDAV API, somebody is overloading the meaning.
508 Loop Detected
Infinite recursion in WebDAV PROPFIND operations. Very rare.
511 Network Authentication Required
Captive portal codes. The WiFi at a hotel or airport sends 511 to tell your browser “you need to log in to the portal first.” The response includes a Location to the portal page.
Troubleshooting matrix: which layer to check first
| Code | App | Proxy | DB | Network |
|---|---|---|---|---|
500 | Yes | — | Maybe (uncaught DB error) | — |
502 | — | Yes (upstream malformed) | — | Maybe (TCP reset) |
503 | Yes (maintenance flag) | Yes (rate-limit reject) | — | — |
504 | Yes (slow handler) | Yes (timeout config) | Yes (slow query) | Yes (DNS, packet loss) |
Common HTTP status code anti-patterns
These five mistakes account for most of the bad code I review.
Anti-pattern check.
1. Wrapping errors in 200 OK
HTTP/1.1 200 OK
{"success": false, "error": "user_not_found"}
Every monitoring tool, CDN, and cache now thinks the request succeeded. Retry logic fails. Status-code-aware load balancers route bad traffic to “healthy” backends. This pattern came from JSON-RPC and was inherited by GraphQL; GraphQL does it because partial successes need per-field error reporting, which is fair. REST has no excuse: use 4xx for client errors, 5xx for server errors, and put the structured detail in the body.
Anti-pattern check.
2. Mixing 401 and 403
If your 401 and 403 aren’t consistent, attackers can probe your API to discover which resources exist. Pick a policy: either return 404 for “you can’t see this” (GitHub’s approach for private repos) or return 403 consistently. Inconsistency leaks information.
Anti-pattern check.
3. Hiding 403 behind 404
Sometimes correct, often a bug. GitHub returning 404 for private repos is deliberate because the repo’s existence is itself sensitive. But if your API returns 404 for “this user account is suspended,” now legitimate users can’t tell if they typed the username wrong or got suspended. Document your policy explicitly and apply it consistently.
Anti-pattern check.
4. Using 500 as the default catch
Frameworks make this easy, which is the problem. Every uncaught exception becomes 500 and your alerting can’t distinguish “database is down” from “user passed a malformed UUID.” Catch validation errors and raise 400 or 422. Catch NotFound from your ORM and raise 404. Reserve 500 for truly unexpected failures, and when you raise it, log a request ID so you can correlate.
Anti-pattern check.
5. Long redirect chains
Each hop costs a round trip. If /old redirects to /intermediate redirects to /canonical, that’s two extra DNS lookups and two extra TCP handshakes (worst case). Google specifically downgrades crawl priority for chains longer than 3 hops, and browsers cap redirect chains at around 20 to prevent loops. Collapse chains at the source: your CDN config or your application’s redirect map.
HTTP status codes and SEO
Search engines treat status codes as authoritative signals about whether to keep, drop, or transfer a URL. Get them wrong and your rankings move.
301 vs 302 (link equity)
301 Moved Permanently transfers PageRank; Google treats the new URL as the canonical destination of all signals pointing at the old URL. 302 Found does not transfer link equity (or transfers it slowly, depending on Google’s heuristics). If you renamed a URL forever, use 301. If you redirect a guest to /login, use 302.
404 vs 410 vs Soft 404
Google distinguishes three “missing” states. A 404 Not Found keeps the URL in the index for a while, and Google rechecks periodically. A 410 Gone drops the URL faster, often within a single crawl cycle. A “Soft 404” is Google’s term for a page that returns 200 OK but renders a “not found” message; Google detects this from content patterns and treats it as a 404 anyway, but you’ve wasted a crawl request and possibly diluted your real content.
If you’re cleaning up a stale index, return real 410s for permanently removed URLs.
5xx and crawl budget
Google’s crawler reduces its rate when a site returns persistent 5xx. Search Console’s Crawl Stats report shows this; a sustained spike of 5xx errors can drop your crawl budget for days, which means new pages take longer to get indexed. Treat 5xx rates as an SEO metric, not just a reliability metric.
200 OK that’s actually broken
Returning 200 OK with an error page (the soft-404 anti-pattern) is the worst case for SEO. Google indexes the error message, ranks it for nothing, and slowly figures out the page is broken. Always return the right status code from the server, even when your single-page app renders a friendly error UI.
How to inspect HTTP status codes (tooling)
You can’t fix what you can’t see. Every working developer should be fluent in at least three of these.
Browser DevTools Network panel
Chrome, Firefox, and Safari all show a Status column in the Network tab. Right-click the column header to add Status Text if it’s not visible.
Useful tricks: Preserve log keeps entries across navigations so you can see the full redirect chain; Filter by status lets you type status-code:5xx (Chrome) to see only server errors; Replay XHR lets you right-click any request and re-run it without reloading the page.
For redirects, expand the request to see every hop and the status code at each step.
curl (the universal answer)
curl shows everything. Three patterns that solve 90% of debugging:
# Just the status code
curl -o /dev/null -s -w "%{http_code}\n" https://api.example.com/users/1
# Headers only (HEAD request, follow redirects)
curl -I -L https://example.com
# Full verbose with request and response headers
curl -v https://api.example.com/users/1
When you’re constructing test URLs with special characters in query strings, use --data-urlencode to let curl handle the encoding for you, or paste the URL into our URL Decoder & Encoder to verify what bytes will actually go on the wire.
# curl encodes the query value for you
curl -G "https://api.example.com/search" \
--data-urlencode "q=hello world & friends"
# Sends: GET /search?q=hello%20world%20%26%20friends
JavaScript fetch
The Response.status property has the integer code. Response.ok is true for any 2xx.
const res = await fetch('https://api.example.com/users/1');
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.ok); // true
if (!res.ok) {
if (res.status === 401) {
// refresh token and retry
} else if (res.status === 429) {
const retryAfter = Number(res.headers.get('Retry-After')) || 1;
await new Promise(r => setTimeout(r, retryAfter * 1000));
} else if (res.status >= 500) {
throw new Error(`Server error: ${res.status}`);
}
}
In axios, the same logic lives in interceptors:
import axios from 'axios';
axios.interceptors.response.use(
response => response,
error => {
const status = error.response?.status;
if (status === 401) {
// redirect to login
}
return Promise.reject(error);
}
);
Python requests
import requests
r = requests.get('https://api.example.com/users/1')
print(r.status_code) # 200
print(r.reason) # 'OK'
# Raises requests.exceptions.HTTPError for 4xx/5xx
r.raise_for_status()
# Manual handling
if r.status_code == 429:
retry_after = int(r.headers.get('Retry-After', '1'))
time.sleep(retry_after)
elif 500 <= r.status_code < 600:
raise RuntimeError(f'Server error: {r.status_code}')
raise_for_status() is the Python idiom for “fail loudly on 4xx/5xx.” Use it in scripts where you want exceptions on errors instead of branching on status_code.
Postman and Bruno
Both let you assert on status codes inside a test script:
// Postman/Bruno test script
pm.test("Status is 201", () => {
pm.response.to.have.status(201);
});
pm.test("Has Location header", () => {
pm.expect(pm.response.headers.get('Location')).to.match(/^\/users\/\d+$/);
});
Run these against staging in CI to catch contract violations before production.
FAQ
What’s the difference between 401 and 403?
401 Unauthorized means the server doesn’t know who you are; your credentials are missing, expired, or invalid. 403 Forbidden means the server knows who you are and is refusing anyway. If sending different credentials might fix it, use 401. If it won’t, use 403.
When should I use 301 vs 302?
Use 301 when the move is permanent: the old URL will never come back, and you want search engines to transfer link equity to the new URL. Use 302 for temporary redirects where the original URL is still canonical (login flows, A/B testing, maintenance pages). For APIs, prefer 308 and 307 because they preserve the request method.
What does a 502 Bad Gateway error mean?
502 means a reverse proxy or load balancer received an invalid response from the upstream server. The upstream replied, but with garbage: wrong protocol, malformed headers, or a dropped connection. It’s different from 504 Gateway Timeout, where the upstream didn’t reply at all. First place to check: origin server logs for crashes or truncated responses.
What is a “soft 404”?
A “soft 404” is a page that returns 200 OK but actually shows a “not found” message. Google detects these heuristically and treats them as 404s anyway. They waste crawl budget and can dilute your real content. Always return real 404 or 410 status codes from the server, even if your single-page app renders a friendly error UI.
When should I use 422 instead of 400?
Use 400 Bad Request when the server can’t even parse the request: malformed JSON, missing structural fields, syntax errors. Use 422 Unprocessable Content when the request parses fine but fails business validation: invalid email format, value out of range, semantically inconsistent fields. The shorthand: 400 for syntax, 422 for semantics.
How do I respond to 429 Too Many Requests?
Read the Retry-After header (a number of seconds or an HTTP date) and back off for at least that long before retrying. If Retry-After is missing, use exponential backoff with jitter starting around 1 second. Never retry immediately; that’s how you get banned.
Are 1xx informational codes still used in 2026?
Yes, but most are invisible to application code. 100 Continue and 101 Switching Protocols are baseline HTTP/1.1 features. 103 Early Hints is increasingly used by Cloudflare, Fastly, and Vercel to push preload hints before the main response, and it noticeably improves Largest Contentful Paint. Most HTTP libraries collapse 1xx into the final response, so you typically see them only in DevTools or curl -v.
Is 418 “I’m a teapot” a real status code?
Yes, surprisingly. RFC 2324 was an April Fools joke from 1998, but enough products implemented it that the IETF kept it on the books in RFC 7168. Don’t ship 418 in production; many reverse proxies and load balancers don’t handle it correctly, and it serves no real purpose outside the joke.