The four codes that matter
There are more 3xx codes than this, but in web work you deal with four. Two dimensions, permanent or temporary and method-preserving or not, and every combination has a code.
| Code | Meaning | Method preserved | Cached by default |
|---|---|---|---|
| 301 | Moved permanently | No | Yes, heuristically |
| 302 | Found (temporary) | No | No |
| 307 | Temporary redirect | Yes | No |
| 308 | Permanent redirect | Yes | Yes, heuristically |
308 is the youngest of the set. It was specified in RFC 7538 in April 2015, which replaced the experimental RFC 7238 with, in the RFC’s own words, “no technical changes”. That late arrival is why plenty of tooling defaults to 301 and 302: those two are as old as HTTP/1.0, and the method-preserving pair arrived after everyone had already built their habits.
The “cached by default” column in that table is the one nobody reads, and it is where the pain lives.
A wrong 301 never goes away
RFC 9110 section 15.4.2 allows a cache to store a 301 using a freshness heuristic when the response carries no explicit expiration. In browsers that plays out as: stored, and kept until the user clears their cache. There is no expiry you can wait out, no purge you can trigger, no header you can send later. The browser will not ask your server again, because you already told it not to.
So a 301 published by accident is not a five-minute mistake. Fix the server, deploy, and every returning visitor whose browser stored the bad redirect keeps landing on the wrong URL. New visitors see the corrected version, which makes the bug report read like “it works for me”. We have watched this eat an afternoon: the only reliable reproduction was a fresh browser profile, and the only fix for affected users was clearing their cache.
You can stay out of this entirely. Use 302 or 307 whenever there is any chance the move is not final, and consider it final only once the new URLs have been live for a while. And send a Cache-Control header with your 301s: max-age=0, must-revalidate keeps the SEO semantics of a permanent redirect while leaving you an undo button, because the browser revalidates instead of resolving from cache. Search engines look at the status code, not at the caching policy, so you lose nothing. More on the header side in our guide to HTTP caching headers.
The same trap in a nastier form is HSTS. A Strict-Transport-Security header with a long max-age makes the browser skip http entirely, and that lives in the browser exactly like a cached 301 does. Sending it with max-age=31536000 before your certificate story is sorted is how a subdomain becomes unreachable for a year.
The POST that becomes a GET
Both 301 and 302 carry the same warning in the spec, sections 15.4.2 and 15.4.3 of RFC 9110: “For historical reasons, a user agent MAY change the request method from POST to GET.” Not must, not must not. May. Browsers do it, and they have done it since the mid-nineties, because early implementations shipped it before anyone wrote it down and the spec eventually documented reality.
The failure mode is quiet. A form POSTs to /api/subscribe, a rewrite rule sends it to /api/subscribe/ with a 301, and the server receives a GET with no body. Your endpoint answers 405, or worse it answers 200 and does nothing. Nobody suspects a trailing slash.
That is the entire reason 307 and 308 exist. Same permanence semantics, but the method and body are carried through unchanged. For anything under an API path, use them. And in a browser context there is a second reason to care: the CORS spec forbids redirects on preflight requests altogether, so an OPTIONS request that hits a redirect fails with a generic CORS message that says nothing about redirects. We covered that one in CORS errors explained.
What Google actually does
The most durable myth in this area is that redirects leak link value. Gary Illyes from Google said on 26 July 2016 that 30x redirects do not lose PageRank, and John Mueller had said the same thing earlier that year. Before that, the folklore number was a loss of roughly 15 percent per hop, which came from how PageRank damping was described for ordinary links. It has been wrong for a decade and it is still in circulation.
What Google’s documentation does distinguish is which URL ends up in search results. Permanent redirects, meaning 301 and 308, make the target the canonical URL and show it in results. Temporary redirects, meaning 302, 303 and 307, keep the source URL in results, since you told Google the original is coming back.
That last part has an escape hatch that trips people up in both directions. Leave a 302 in place long enough and Google eventually decides the move was permanent anyway and swaps to the target, because canonicalisation groups both URLs and picks a winner. Which means a 302 is not a way to keep the old URL ranking indefinitely, and it also means the usual panic about accidentally shipping a 302 during a migration is overdone. Fix it, and the outcome converges.
One thing that does destroy value: redirecting into something Google cannot use. A target blocked in robots.txt, carrying a noindex, or answering 404 breaks the chain completely. Redirecting a retired section wholesale to the homepage counts too, since Google treats those as soft 404s rather than as a move. Point each URL at its closest equivalent, or let it 410 honestly.
Chains, loops and hop limits
Redirect chains grow on their own. The http to https rule from 2017, the www consolidation from 2019, the CMS migration from 2022, the trailing-slash normalisation someone added last spring. Each was one hop when it shipped. Together they are four, and nobody ever tested the combination.
The limits worth remembering:
- Google follows up to 10 hops by default, per its crawler documentation, and reports anything beyond that as a redirect error in Search Console. John Mueller has recommended keeping frequently crawled URLs under five.
- curl allows 50 when you pass
-L, which is why chains that browsers handle badly still look fine in your terminal.curl -sILprints every hop, and that is the fastest way to see a chain you did not know you had. - Browsers give up well before either number and show ERR_TOO_MANY_REDIRECTS, usually because two rules disagree: a canonical rule that adds a trailing slash and a framework route that removes it, or an app forcing https behind a proxy that already terminated TLS and speaks http internally.
The proxy loop deserves a note because it is so common. Your load balancer terminates TLS and forwards plain http, your app sees http and redirects to https, the load balancer terminates it again and forwards http again. Infinite. The fix is to trust X-Forwarded-Proto instead of the connection scheme, which is a one-line setting in most frameworks and a full outage until someone finds it.
When you flatten a chain, rewrite the map so every old URL points straight at the final target. Not at the intermediate one. Redirect maps are worth keeping in version control as plain data, and if yours lives in a spreadsheet, our JSON to CSV converter runs entirely in your browser, which matters when the file is a full URL inventory of a client’s site.
Where redirects live now
If your redirect knowledge stops at .htaccess, half of it no longer applies. On static and serverless hosting there is no Apache to configure, and redirects are declared in a config file that is part of the deployment.
On Vercel that is a redirects array in vercel.json, with a boolean permanent flag rather than a status code: true sends a 308, false sends a 307. Note that both are the method-preserving codes, not 301 and 302, which occasionally surprises people auditing a site with a crawler. On Netlify the same job is done by a _redirects file or the [[redirects]] blocks in netlify.toml, where the default status is 301 and an exclamation mark after the code forces the rule even when a file exists at that path. Cloudflare Pages uses a _redirects file too, with its own limits on the number of rules.
Two consequences of moving redirects into the repo, both good. They get code review and they ship with the deploy that needs them, instead of being a manual step someone does in a control panel at 11pm. And they are diffable, so the chain that grew over four years is visible in one file instead of spread across a server config, a CDN rule and a middleware.
Framework-level redirects (Next.js config, Astro middleware, Express handlers) work as well, but they run after the request has reached your application, so they cost a function invocation that an edge-level rule does not. For a large permanent map, put it at the edge.
Choosing one in practice
Four things worth checking in any redirect layer you inherit:
- Permanent move of a page or domain: 301, with
Cache-Control: max-age=0, must-revalidateso a mistake stays fixable. - A/B tests, geo or language routing, anything that depends on who is asking: 302, and send
Varyplus a no-store cache header. A cached redirect that sends every visitor to the German version because the first one came from Vienna is a fun bug to find. - Maintenance windows and temporary landing pages: 302, never 301. This is the single most expensive accidental 301 in the wild.
- Anything with a request body, anything under /api: 307 or 308.
- Login flows returning to a page after authentication: 302 or 303, and validate the target against an allowlist. Open redirects are a real vulnerability class, and “redirect to whatever is in the query string” is how you become part of someone else’s phishing chain.
One last piece of sequencing. If a migration changes hostnames as well as URLs, the redirects and the DNS change land at the same time, and only one of the two is reversible in a hurry. Get the record TTLs down before the switch, which is a different waiting game with its own rules: see why DNS propagation is a myth.
Redirect questions that keep coming back
What is the difference between a 301 and a 302 redirect?
A 301 says the resource has moved permanently, a 302 says it is somewhere else for now. The difference that matters day to day is caching, not semantics: a 301 may be stored by browsers and caches even without any cache headers, while a 302 is not cacheable unless the response explicitly says so. For search engines, Google shows the target URL for a permanent redirect (301 and 308) and keeps showing the source URL for a temporary one (302, 303 and 307).
Do 301 redirects hurt SEO?
No. Gary Illyes of Google stated on 26 July 2016 that 30x redirects do not lose PageRank, which retired the old rule of thumb that every redirect cost about 15 percent of link value. What still costs you is everything around the redirect: the extra round trip before the page starts loading, chains that pile up over the years, and redirects pointing at pages that no longer exist. The status code is rarely the problem, the plumbing is.
How long should I keep a 301 redirect in place?
Treat 301 redirects as permanent parts of the site, not as a temporary migration step. Google needs the redirect until it has recrawled and swapped every affected URL, which for a large or rarely crawled site can take months, and external links plus bookmarks keep sending traffic for years after that. We keep migration redirect maps forever and only ever consolidate chains, because removing a redirect turns a working link into a 404 with no warning.
How do I clear a cached 301 redirect in Chrome?
Open devtools, tick "Disable cache" in the Network tab and reload with devtools open, or clear "Cached images and files" in the browsing data dialog. A hard reload alone is often not enough, because the redirect is resolved before the request for the page is even made. The lasting fix is on the server: send Cache-Control: max-age=0 or no-store together with the 301 so browsers stop storing it, and use a 302 or 307 while you are still unsure whether the move is final.
When should I use a 307 or 308 redirect?
Use 307 and 308 whenever the request method has to survive the redirect, typically for APIs and form submissions. Both 301 and 302 carry a note in the HTTP spec that a user agent may change the request method from POST to GET "for historical reasons", which means a POST can silently arrive at the target as a body-less GET. 307 (temporary) and 308 (permanent) were defined precisely to remove that ambiguity, 308 in RFC 7538 from April 2015.
Does a redirect pass PageRank?
Yes. Since Google confirmed it publicly in 2016, all 3xx redirects pass PageRank without dilution, and that includes 302s. The practical caveat is that a redirect only passes anything if Google can follow it, so the target has to return a 200 and must not be blocked by robots.txt or noindexed. A redirect to a page that Google is not allowed to crawl loses everything the source page had.
How many redirects in a chain are too many?
Google says its crawlers follow up to 10 redirect hops by default and treat anything beyond that as a redirect error, and John Mueller has recommended staying under five for frequently crawled URLs. For users the number that counts is much smaller, because every hop is a full round trip, which on mobile networks can mean a few hundred milliseconds each. Anything past two hops is worth flattening, and http to https plus a trailing-slash rule plus a domain change gets you to three without anyone noticing.
Should I redirect http to https and www to non-www?
Yes, with a single 301 that fixes both at once instead of two chained rules. The usual accident is a rule set where http://www.example.com goes to https://www.example.com and only then to https://example.com, which is two hops on every single non-canonical entry. Write the rule so that any non-canonical combination lands on the final URL in one step, and add HSTS afterwards so browsers upgrade http to https on their own without a request at all.