301, 302, 303, 307 and 308: what actually differs
A redirect is a 3xx response with a Location header. The codes differ on two points: whether the move is permanent, and whether the client may change the request method. The definitions are in RFC 9110.
- 301 Moved Permanently: permanent. For historical reasons, clients may turn a POST into a GET when following it, and browsers do.
- 302 Found: temporary, with the same historical POST-to-GET exception.
- 303 See Other: tells the client to fetch the target with GET (or HEAD). Typically used after a form submission to show a result page.
- 307 Temporary Redirect: temporary, and the method and body must not change.
- 308 Permanent Redirect: permanent, and the method and body must not change.
301 and 308 are cacheable by default, so a browser may reuse them without asking the server again; 302 and 307 are cached only when the response carries explicit caching headers.
Trace the chain with curl
curl -sIL follows every redirect and prints the headers of each response:
curl -sIL http://example.com/ | grep -iE '^(HTTP|location)'Typical output for a two-hop chain:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
HTTP/2 301
location: https://www.example.com/
HTTP/2 200-I sends HEAD requests, which some servers answer differently from GET. The first command below traces with GET and dumps only the headers; the second prints a one-line summary with the hop count and final URL:
curl -sL -o /dev/null -D - https://example.com/
curl -sL -o /dev/null -w '%{num_redirects} hops, final: %{http_code} %{url_effective}\n' http://example.com/Test every entry point, because each one can take a different path:
for u in http://example.com/ http://www.example.com/ https://example.com/ https://www.example.com/; do
echo "== $u"; curl -sIL --max-redirs 10 "$u" | grep -iE '^(HTTP|location)'
donecurl only follows Location headers. It runs no JavaScript or meta refresh and, by default, keeps no HSTS or redirect cache between runs, which makes it a good baseline to compare with what a browser shows.
Trace it in browser devtools
In the Network panel, enable Preserve log (Chrome, Edge) or Persist Logs (Firefox) so navigations do not clear the list, tick Disable cache, then load the starting URL. Filter on document requests: each hop appears as a row with its status code and Location response header.
Watch for rows that never reached the server. Chrome labels a redirect reused from its cache “from disk cache”, and shows an HSTS upgrade as 307 Internal Redirect with the header Non-Authoritative-Reason: HSTS. Both are produced by the browser itself, not by your server.
Common problems
- Redirect loops: A redirects to B and B back to A, until the browser fails with an error such as
ERR_TOO_MANY_REDIRECTS. A frequent cause: a CDN or load balancer terminates HTTPS and reaches the origin over HTTP, and the origin, unaware of the original scheme (for example because it ignoresX-Forwarded-Proto), keeps redirecting to HTTPS. - Too many hops: rules added over the years stack up, e.g.
http://example.com/page→https://example.com/page→https://www.example.com/page→https://www.example.com/page/. Each hop is another round trip. Point every rule straight at the final URL. - The http → https → www chain: usually worth collapsing to one hop, with one exception. If you want HSTS on the bare domain or plan to submit it to the HSTS preload list,
http://example.comshould first redirect tohttps://example.comon the same host, so the browser receives the HSTS header there. - Mixed canonical signals: redirects send users to
wwwwhilerel="canonical", internal links or the sitemap still use the bare domain, HTTP or another trailing-slash form. - Method change on 301, 302 or 303: a POST to an old endpoint becomes a GET after the redirect, the body is dropped, and the form or API call fails. Use 307 or 308 when the method must survive.
- Cached redirects and HSTS: once a browser has received a
Strict-Transport-Securityheader, it rewriteshttp://tohttps://itself, without any network request, untilmax-ageexpires. A cached 301 is reused in a similar way. Fixing the server will not change what that browser shows, so test with curl or a fresh browser profile. In Chrome, dynamically learned HSTS entries can be deleted atchrome://net-internals/#hsts; preloaded ones cannot. - Meta refresh and JavaScript redirects: the server returns 200, then the page navigates away with
<meta http-equiv="refresh" content="0; url=/new">orwindow.location. Header-based tools do not see them. Find them withcurl -s https://example.com/ | grep -iE 'http-equiv|location\.(href|replace)'and replace them with server-side redirects where you can.
SEO implications
- Keep chains to a single hop. Every extra hop adds latency for users and crawlers, and crawlers stop following after a limited number of hops.
- Use a permanent redirect (301 or 308) for permanent moves. A temporary code tells search engines the original URL may come back.
- Make the redirect target the canonical URL: it should return 200, carry a self-referencing
rel="canonical", and be the URL used in internal links, sitemaps and hreflang annotations. - Redirect old URLs to their closest equivalent rather than sending everything to the home page, and keep redirects in place long after a migration.
- Prefer server-side redirects. Meta refresh and JavaScript redirects only take effect after the page has been fetched (and, for JavaScript, rendered), and not every crawler handles them.
Quick checklist
- Trace all four variants:
httpandhttps, bare domain andwww. - Each one reaches the final URL in one hop (two if you deliberately keep the HSTS step).
- Permanent moves use 301 or 308; endpoints that receive POST use 307 or 308.
- The final URL returns 200 and is self-canonical.
- Internal links, the sitemap and canonical tags use the final URL.
- No loops behind your CDN or proxy: test with curl, not only a browser.
- The page body contains no meta refresh or JavaScript redirect.
- Re-test in a fresh browser profile to rule out cached redirects and HSTS.
Check it with WebTraceCheck
To check a chain without installing anything, start a website check on WebTraceCheck; no account is needed. The audit inspects HTTP responses, redirects, DNS and technical SEO, and lists each observation with its source and the limits of the measurement. To see what a real browser does, sign in with GitHub and open a temporary cloud browser with the engine, region and device mode you choose, separate from the everyday browser where your cached redirects and HSTS entries have built up.