HTTP Status Codes
200, 301, 302, 304, 307, 308, 404, 410, 451, 500, 503 — what each means to a crawler, when to use which, and how to diagnose redirect chains and soft 404s.
HTTP status codes are how your server tells crawlers what just happened. 200 means “here’s the page.” 301 means “moved permanently — pass the link equity.” 404 means “I have no record of this.” 503 means “I’m broken, come back later.” Wrong status codes are quietly destructive: a 302 used in place of a 301 leaks ranking signal for months.
Redirect chain simulator
Define your URL rules, pick a starting URL, watch Googlebot walk the chain. Spot loops, dead ends, and equity bleed.
URL rules
Crawler trace
Moved Permanently — Permanent redirect. Equity transfers; canonical updates over time.
/best-crm-2023
Moved Permanently — Permanent redirect. Equity transfers; canonical updates over time.
/best-crm-2024
Found (temporary) — Temporary redirect. Google passes equity post-2016 but treats it as soft.
/best-crm-software
OK — Final destination — content served.
Resolved cleanly in 4 hops — equity preserved 89%
3 redirect hops — collapse intermediate 301s into a single hop to maximise equity and crawl budget.
In production, change the second 301 above to a 302 and watch the equity number drop. Add a rule pointing back to an earlier URL to trigger a loop. The simulator stops at 12 hops to mirror browser/crawler behaviour.
TL;DR
- 301 vs 302 still matters in 2026, despite Google’s “we treat them similarly” language. John Mueller and Gary Illyes have both confirmed Google passes link equity through both, but 302s leave the original URL in the index for longer and create ambiguity that delays consolidation. Use 301 for permanent moves, period.
- Soft 404s are the silent killer. A page that returns 200 OK but renders “no results” content is treated as a soft 404 by Google’s classifier and gets dropped from the index. Common culprits: empty search results, expired listings, deleted profile pages.
- Use 410, not 404, when content is permanently gone. Google deindexes 410 URLs roughly 2x faster than 404s. For removed products, deleted accounts, or expired campaigns, 410 is the honest signal.
The mental model
HTTP status codes are like the post office’s stamps on returned mail. Each stamp tells the sender something different: “delivered” (200), “moved, here’s the new address” (301), “moved temporarily, write to old address” (302), “no such address, never was” (404), “this address has been retired” (410), “the post office is closed today” (503).
Crawlers calibrate their behavior to these stamps. Googlebot retries 503s (assumed temporary) for up to ~14 days before treating the URL as permanently lost. It deindexes 410s faster than 404s. It follows 301 chains up to about 5 hops before giving up. It treats 451 (“unavailable for legal reasons”) as honest — the URL stays in the index but is hidden in the affected jurisdiction.
A status code is also a contract about caching. 304 Not Modified is a 200’s lighter sibling: the server confirms the content has not changed since the client’s last fetch, saving bandwidth and signaling stable freshness. Browsers, CDNs, and Googlebot all respect 304s when they have the conditional headers (If-Modified-Since, If-None-Match) to make the request.
Deep dive: the 2026 reality
The status codes every SEO must know cold:
| Code | Name | Meaning to Googlebot | When to use |
|---|---|---|---|
| 200 | OK | Index this page | Normal content delivery |
| 301 | Moved Permanently | Replace old URL with new in index | Permanent URL change |
| 302 | Found / Temporary Redirect | Keep old URL; new URL is temporary | A/B tests, geo-redirects, login flows |
| 304 | Not Modified | Don’t recrawl; reuse cached copy | Conditional requests with If-Modified-Since |
| 307 | Temporary Redirect (strict) | Like 302 but preserves HTTP method | API redirects, POST preservation |
| 308 | Permanent Redirect (strict) | Like 301 but preserves HTTP method | Permanent moves where method matters |
| 404 | Not Found | Drop URL after extended absence | Deleted page, no replacement |
| 410 | Gone | Drop URL faster than 404 | Permanently retired content |
| 451 | Unavailable For Legal Reasons | Hide in affected region; keep elsewhere | DMCA, GDPR, court order |
| 500 | Internal Server Error | Crawl error; retry later | Don’t use deliberately — fix the bug |
| 503 | Service Unavailable | Temporarily skip; retry up to ~14 days | Maintenance, rate limit |
| 429 | Too Many Requests | Slow crawl rate | Rate limiting; honored by Bingbot, partially by Googlebot |
301 vs 302 in 2026. Google’s official line for years has been “PageRank flows through both.” Both Mueller (2016) and Illyes (2020, 2024) have repeated this. In practice, the index behavior differs:
- A 301 typically deindexes the source URL within 1–4 weeks of stable observation.
- A 302 keeps the source URL indexed indefinitely; if you redirect from
/old/to/new/with a 302, both URLs may stay in the index, with/old/outranking/new/for months. - 302s on home/category-level redirects are particularly disruptive — they suggest the redirect is testing, and Google holds the index in suspense.
Use 301 unless the redirect is genuinely temporary. Document any 302 in your codebase with a comment explaining why.
Soft 404 detection. Google flags pages as soft 404 when:
- The HTTP status is 200 but the rendered content reads like a 404 (small text, “Page not found,” “no results”).
- The page has substantially less content than other pages on the site.
- The URL is reachable but the resource it depended on (a product, a profile) was deleted.
The fix is binary: either return a real 404/410 status or beef up the page with genuine content. Empty search results pages are the most common cause; either return 410 for queries with zero results or render meaningfully different content per query.
Redirect chains and loops. Each hop costs crawl budget and a small amount of link equity. Google has repeatedly stated it follows up to 5 hops in a chain; beyond that it gives up and may not pass full equity. Loops (A → B → A) are a hard fail and the URL is treated as broken. Bingbot is stricter — it follows up to 3 hops.
Visualizing it
flowchart TD
A[Bot requests URL] --> B{Server responds with status}
B -->|2xx| C[Read content]
B -->|301| D[Follow to new URL, mark old as moved]
B -->|302| E[Follow temporarily, keep old indexed]
B -->|304| F[Use cached copy]
B -->|404| G[Mark missing, retry occasionally]
B -->|410| H[Drop from index quickly]
B -->|451| I[Index but hide in jurisdiction]
B -->|500| J[Treat as transient error, retry]
B -->|503| K[Skip, retry within 14 days]
D --> L{Chain depth>5?}
L -->|Yes| M[Give up, treat as broken]
L -->|No| C
Bad vs. expert
The bad approach
The classic redirect chain born from years of incremental URL changes:
GET /products/red-widget
→ 301 /products/widget-red
→ 301 /products/widgets/red
→ 301 /shop/widgets/red
→ 301 /shop/widgets/red/
→ 200 OK
Five hops to deliver the same product page. Googlebot follows it (just barely), but every hop costs crawl budget across thousands of similar URLs. Bingbot drops the chain at hop three.
The soft 404 pattern, often produced by ecommerce inventory systems:
<!-- /products/discontinued-widget returns 200 OK -->
<h1>Sorry, this product is no longer available</h1>
<p>Browse our <a href="/products">full catalog</a>.</p>
The status is 200; the content is empty; Google’s classifier flags this as a soft 404 and drops the URL. The product gets no SEO value during its phase-out window, and external links to it bleed equity.
The silent 302:
# Bad: 302 used for a permanent move
location /old-blog {
return 302 /blog;
}
Six months later, /old-blog still ranks for the brand query while /blog languishes. The team blames “Google” when the cause is one missing digit.
The expert approach
Flatten redirect chains to one hop. Maintain a redirect map keyed to the original URL, not the most recent one:
# Each old URL points directly to the final destination
map $request_uri $final_redirect {
~^/products/red-widget$ /shop/widgets/red/;
~^/products/widget-red$ /shop/widgets/red/;
~^/products/widgets/red$ /shop/widgets/red/;
~^/shop/widgets/red$ /shop/widgets/red/;
default "";
}
server {
if ($final_redirect != "") {
return 301 $final_redirect;
}
}
Real 410 for permanently retired URLs:
location ~ ^/products/(discontinued-widget|expired-bundle-2024)$ {
return 410;
}
Custom 410/404 page that helps users without faking the status:
<!-- Served with HTTP 410 status, not 200 -->
<!doctype html>
<html lang="en">
<head>
<title>This product is no longer available</title>
<meta name="robots" content="noindex">
</head>
<body>
<h1>This product is no longer available</h1>
<p>You might be looking for one of these:</p>
<ul>
<li><a href="/shop/widgets/red/">Red widget (replacement)</a></li>
<li><a href="/shop/widgets/">Browse all widgets</a></li>
</ul>
</body>
</html>
Maintenance window with 503 + Retry-After:
location / {
return 503;
add_header Retry-After "Sat, 10 May 2026 14:00:00 GMT";
}
Conditional 304s for static-ish content via Last-Modified and ETag:
GET /blog/canonicals HTTP/1.1
If-Modified-Since: Wed, 01 May 2026 12:00:00 GMT
HTTP/1.1 304 Not Modified
ETag: "abc123"
Last-Modified: Wed, 01 May 2026 12:00:00 GMT
Do this today
- Pull a Screaming Frog SEO Spider crawl. Open Response Codes > Redirection (3xx) and Reports > Redirects > All Redirects. Anything with Chain Length > 1 is a hop you can flatten.
- In Screaming Frog Reports > Redirects > Redirect Chains, export and review. For each chain, update your
nginx/Cloudflare/CDN map to point the original URL directly at the final URL. - Filter Screaming Frog Response Codes > Client Error (4xx) for
404. For each 404 with significant inbound internal links, decide: is there a relevant replacement (301), or is the content gone (return 410)? - In GSC > Indexing > Pages, expand Not indexed > Soft 404. Each URL listed is leaking value. Visit each and decide: return real 410, render genuine content, or 301 to a related URL.
- Check 302 usage. Search your config and codebase for
302andFound. Each instance needs a comment justifying why this is temporary. Convert any without justification to 301. - Verify your maintenance / rate-limit story uses 503, not 500. Test by making a request to your maintenance page and confirming
curl -IreturnsHTTP/1.1 503 Service Unavailableplus a saneRetry-Afterheader. - Set up Bing Webmaster Tools > Crawl Information monitoring. Bing reports 4xx/5xx more aggressively than GSC and surfaces issues earlier — especially relevant since ChatGPT Search and Copilot pipe through Bing.
- For non-HTML resources, run
curl -Ion representative URLs (PDFs, images, CSV downloads). Confirm they return 200 with sensible cache headers, not 304 errors or 5xx. - Add a synthetic monitor (Pingdom, Uptime Robot, or your APM) that asserts your homepage returns 200, your sitemap returns 200, your
robots.txtreturns 200, and a known-deleted URL returns 410. Alert on any drift. - For migrations, build a status-code regression test that runs after every deploy: a CSV of 50 URLs and their expected status codes, plus a script that hits each and fails the deploy on mismatch.
Mark complete
Toggle to remember this module as mastered. Saved to your browser only.
More in this part
Part 5: Technical SEO
- 026 Technical SEO Fundamentals 12m
- 027 Site Architecture 20m
- 028 Crawling & Indexing 17m
- 029 robots.txt Deep Dive 15m
- 030 XML Sitemaps 12m
- 031 Canonical Tags 20m
- 032 Meta Robots & X-Robots-Tag 13m
- 033 HTTP Status Codes You're here 15m
- 034 Crawl Budget Management 16m
- 035 JavaScript SEO 26m
- 036 Core Web Vitals 17m
- 037 Site Speed & Performance 19m
- 038 HTTPS & Site Security 12m
- 039 Mobile SEO & Mobile-First Indexing 14m
- 040 Structured Data & Schema Markup 17m
- 041 International SEO (hreflang) 19m
- 042 Pagination 12m
- 043 Faceted Navigation 26m
- 044 Duplicate Content 13m
- 045 Site Migrations 24m