The four redirect codes
HTTP defines several redirect status codes in RFC 9110. Four matter for moving pages; they differ on two questions: is the move permanent, and may the browser change a POST request into a GET?
| Code | Name | Permanent | Request method kept | Typical use |
|---|---|---|---|---|
| 301 | Moved Permanently | Yes | Not guaranteed: clients may switch POST to GET | Page or site moved for good |
| 302 | Found | No | Not guaranteed | Short-term moves, tests, login hand-offs |
| 307 | Temporary Redirect | No | Yes | Temporary moves where forms or APIs POST to the URL |
| 308 | Permanent Redirect | Yes | Yes | Permanent moves of URLs that receive POST requests |
For ordinary pages that people visit with GET, 301 and 308 behave the same, as do 302 and 307. The method rule matters for form endpoints and APIs: a 301 on a URL that receives a POST may arrive at the destination as a GET with the form data dropped. A fifth code, 303 See Other, is the deliberate "now GET this page" response after a form submission.
One practical difference: browsers may cache a 301 or 308 without being told to, so a mistaken permanent redirect can keep sending visitors to the wrong place after you fix the server. Test new rules with a 302, or send Cache-Control: no-store with the 301, and switch once you are sure.
How search engines treat them
Google's documentation describes the difference as a canonicalization signal:
- 301 and 308: Googlebot follows the redirect, and the indexing pipeline uses it as a strong signal that the target should be the canonical URL, the one shown in results.
- 302, 303 and 307: Googlebot follows the redirect, but it is only a weak signal for the target. The original URL usually stays indexed, though the target can still be chosen if other signals point to it.
Does a 302 pass link value? Google's documentation doesn't describe either type as losing value. What it describes is which URL ends up canonical, and that is where links and other signals are consolidated. With a 302, that is usually the old URL; with a 301, the new one. So if you want the new address to rank, use a permanent redirect.
When temporary is the right choice:
- A/B and multivariate tests. Google's testing guidance explicitly says to use a 302, not a 301, and to remove the test when it ends.
- Short-lived swaps: a seasonal page standing in for a product that will return, or a campaign URL that forwards to a page that will change.
- Login and session flows, where the destination depends on the visitor.
For planned downtime, a redirect is the wrong tool: return 503 Service Unavailable so crawlers come back later instead of following a redirect.
Site migrations and URL changes
For a domain move, an HTTPS switch or a new URL structure:
- Map every old URL to its closest new equivalent, one-to-one. Export old URLs from your sitemap, analytics and server logs so pages with backlinks aren't missed.
- Use server-side permanent redirects (301 or 308). Google's site-move guide recommends them over other methods.
- Update everything that points to old URLs: internal links, canonical tags, hreflang, XML sitemaps and structured data should all use the new URLs, so the redirect is only needed for outside links and bookmarks.
- Keep the redirects for as long as possible, in Google's words "generally at least 1 year". Many sites keep them indefinitely, since old links never fully disappear.
- Don't send everything to the homepage. A page with no equivalent should return 404 or 410; a redirect to an unrelated page helps nobody find what they were looking for.
On Apache, the .htaccess redirect generator writes these rules in the right order. Two hand-written examples:
# One page (RedirectMatch anchors the whole path)
RedirectMatch 301 ^/old-page/$ https://www.example.com/new-page/
# Whole domain to a new one, keeping each path
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old-example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]A common Apache pitfall: the simpler Redirect 301 /old-page/ … matches by prefix, so it also redirects /old-page/anything-else. On nginx, use return 301 inside an exact-match location: location = /old-page/ { return 301 https://www.example.com/new-page/; }. To try rules against sample URLs before uploading them, use the .htaccess tester.
Redirect chains and loops
A chain is a redirect that lands on another redirect. Google's crawlers follow up to 10 hops, but its site-move guide advises redirecting "to the final destination directly", and otherwise keeping chains short: "ideally no more than 3 and fewer than 5". Every hop also adds a round trip for visitors on slow connections.
Chains usually build up from separate rules that each do one job:
http://example.com/blog/post
→ 301 https://example.com/blog/post (HTTPS rule)
→ 301 https://www.example.com/blog/post (www rule)
→ 301 https://www.example.com/blog/post/ (trailing-slash rule)
→ 200The fix is to make each rule redirect straight to the final form (HTTPS, preferred host and trailing slash in one go), and to point old migration redirects at the current URL rather than at an address that now redirects again.
A loop is a chain that never ends: two rules undo each other, for example a server adding a trailing slash while the CMS strips it, or an HTTPS redirect behind a CDN that talks to the origin over plain HTTP. Browsers stop with an error such as "too many redirects", and crawlers give up.
Is a meta refresh a redirect? Yes, a client-side one. Google treats an instant <meta http-equiv="refresh" content="0; url=…"> as a permanent redirect and a delayed one as temporary. Use it only when you can't configure the server; JavaScript redirects are a last resort, because they only work if the page is rendered. Timed refreshes are also an accessibility problem: WCAG lists a refresh users can't control as a failure.
Checking your redirects
After any change, check the old URLs, not just the new ones. For each, you want exactly one redirect, with the intended status code, to a final URL that returns 200, isn't blocked by robots.txt or noindex, and has a canonical tag pointing to itself.
From a terminal, curl shows every hop:
curl -sIL http://example.com/old-page | grep -iE "^(HTTP|location)"Without the command line, the redirect checker lists each hop with its status code and Location header, which makes chains and the wrong code (a 302 where you meant 301) easy to spot. For a list of URLs, such as the old URLs from a migration map, an HTTP status checker reports the status of each in one pass.
Then keep watching: Search Console's Page indexing report shows "Page with redirect" for URLs Google has seen redirecting, and server logs show which old URLs still get traffic, which tells you the redirects are still earning their keep.
Sources
- RFC 9110: HTTP Semantics, section 15.4 Redirection 3xx
- Google Search Central: Redirects and Google Search
- Google Search Central: Site moves with URL changes
- Google Search Central: How HTTP status codes and network errors affect Google Search
- Google Search Central: Minimize A/B testing impact in Google Search
- Apache HTTP Server: mod_alias (Redirect, RedirectMatch)
- nginx: ngx_http_rewrite_module (return)
- W3C WCAG 2.2 Technique F41: Failure due to using meta refresh to reload or redirect
