Overview
A redirect maps one URL to another, and its status code decides which URL Google shows in results: permanent redirects show the new target, temporary redirects keep showing the source. Most traffic lost in a URL change comes from sloppy maps (chains, loops, dead targets), not from the code choice. The end-to-end sequence is in seo-migration-playbook.
Use 301 or 308 for permanent moves
Google treats 301 and 308 as a strong signal that the target should be canonical; 302 and 307 do not carry that signal, though the target can still be indexed when other signals point to it. 308 and 307 behave like 301 and 302 for Google; they differ only for other clients, because they preserve the HTTP method.
- Use 301 for slug rewrites, folder restructures, domain moves, duplicate consolidation, HTTP to HTTPS, and trailing-slash enforcement.
- Use 308 when POST or PUT requests must survive the redirect without becoming GET; this is rare on content sites.
- When a moved URL moves again, edit the original redirect to point at the final URL instead of stacking a chain.
location = /old-article {
return 301 /new-article;
}Use 302 or 307 only when the move is temporary
A 302 keeps the source URL in results. Use it for A/B tests, geo-routing, and maintenance pages. If a 302 stands in for a permanent move, the old URL stays the one Google shows until it decides to treat the redirect as permanent. Browsers also issue an internal 307 for HSTS upgrades; you never write that one by hand.
Choose redirect or canonical by what the user should see
Use a 301 when users should never see the old URL again. Use rel="canonical" when both URLs stay reachable and one is preferred for ranking (parameter variants, print versions). Never 301 to a noindex page: the destination drops out of the index and the source goes with it. See technical for canonical mechanics.
Strip tracking parameters and preserve functional ones
Decide per redirect whether the query string carries over.
- Strip tracking parameters (
utm_*,fbclid,gclid) so the destination is not indexed under many variants. - Preserve functional parameters (
?page=2,?lang=fr); stripping them changes the page. - Preserve search parameters (
?q=foo) only when the destination is the same search page.
Nginx return drops the query string unless you append $is_args$args; Apache’s Redirect carries it forward. Check your server’s default.
location = /old-page {
return 301 /new-page$is_args$args;
}Keep chains to one hop and eliminate loops
Googlebot follows up to 10 redirect hops by default; other crawlers may stop sooner, and every hop adds latency for users. Rewrite the redirect map so each old URL points directly at its current final URL. Two hops are tolerable for a sprint during a migration; a loop makes crawlers drop the URL and browsers show an error. The three-hop threshold used in audits is a house rule, not a Google limit.
curl -sIL https://example.com/old-url | grep -E "^(HTTP|Location)"
# Each HTTP line is a hop. Anything beyond one is a fix.Check for these failure modes before every deploy
- Redirect to a
noindexpage or a 404. Validate that every destination returns 200 and is indexable before it enters the map. - Soft 404. A URL returns 200 with an empty or “not found” template, and Google classifies it as a 404. Return a real 404 or 410 (Google treats both the same), or 301 to the closest equivalent page.
- Homepage dump. Redirecting every old URL to the homepage reads as soft 404s at scale and passes no deep-page relevance. Map each URL to its closest new equivalent.
- Meta refresh or JavaScript redirects. Google can follow both, but a server-side redirect is the most reliable, and a JavaScript redirect only works if rendering succeeds. Use them only when the server cannot redirect.
Keep the map in source control (old URL, new URL, status, date) and leave redirects in place for as long as the old URLs have inbound links.