Redirect status codes
HTTP defines four redirect status codes in common use. The key differences are permanence and method preservation.
301 Moved Permanently
The resource has permanently moved to a new URL. Browsers and search engines cache this redirect aggressively. Subsequent requests go directly to the new URL without hitting the old one.
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Method handling: Per the original HTTP/1.0 spec, clients may change the method from POST to GET when following a 301. In practice, most browsers do exactly this. If you need to preserve the POST method, use 308 instead.
Use case: Permanent URL changes, domain migrations, HTTP-to-HTTPS upgrades (the standard choice).
302 Found
The resource is temporarily at a different URL. The client should continue using the original URL for future requests. Search engines do not transfer link equity to the new URL.
HTTP/1.1 302 Found
Location: https://example.com/temporary-location
Method handling: Like 301, browsers historically change POST to GET. The 302 status was widely misused to mean "temporary redirect with method change," which led to the creation of 307 to clarify the semantics.
Use case: Temporary redirects (maintenance pages, A/B testing, login redirects). Do not use 302 for permanent moves -- search engines will keep indexing the old URL.
307 Temporary Redirect
Identical to 302, but the client must not change the HTTP method. A POST request redirected with 307 remains a POST.
HTTP/1.1 307 Temporary Redirect
Location: https://example.com/api/v2/endpoint
Use case: API version redirects, temporary endpoint moves where you need to preserve the request method and body.
308 Permanent Redirect
Identical to 301, but the client must not change the HTTP method. A POST redirected with 308 remains a POST. The redirect is permanent and cacheable.
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/api/v2/endpoint
Use case: Permanent API endpoint moves where method preservation matters. For HTML pages where visitors use GET, 301 and 308 behave identically.
Summary table
Code Permanent? Method preserved? Use for
---- ---------- ----------------- -------
301 Yes No (POST->GET) URL changes, HTTP->HTTPS, domain moves
302 No No (POST->GET) Temporary redirects (login, maintenance)
307 No Yes Temporary API redirects
308 Yes Yes Permanent API redirects
Redirect chains and their cost
A redirect chain occurs when one redirect leads to another. For example:
http://example.com
-> 301 https://example.com (HTTP->HTTPS upgrade)
-> 301 https://www.example.com (naked->www redirect)
-> 301 https://www.example.com/ (add trailing slash)
3 redirects before reaching the final page
Performance impact
Each redirect adds a full HTTP round trip: DNS lookup, TCP connection, TLS handshake (if HTTPS), request, and response. On a mobile connection with 100ms latency, three redirects add 300-600ms before the browser can even start loading the page. This directly increases Time to First Byte (TTFB) and Largest Contentful Paint (LCP).
SEO impact
Search engines follow redirect chains but with diminishing patience. Google has stated that redirect chains do pass PageRank, but:
- Crawl budget is consumed by each hop. On large sites, deep redirect chains mean fewer pages get crawled.
- Mixing 301 and 302 in a chain confuses the signal. A 302 in the middle of a permanent redirect chain may prevent search engines from consolidating link equity.
- Google's crawler has a limit (typically 5 hops) before it stops following redirects.
Best practice: Every redirect should go directly to the final destination in a single hop. Audit your redirect configuration to eliminate intermediate stops.
Optimal redirect pattern
# Bad: chain of redirects
http://example.com -> https://example.com -> https://www.example.com
# Good: single redirect to final destination
http://example.com -> https://www.example.com (one hop)
https://example.com -> https://www.example.com (one hop)
HSTS interaction
HSTS (Strict-Transport-Security) eliminates the HTTP-to-HTTPS redirect for returning visitors. Once a browser has cached the HSTS policy, it internally rewrites http:// to https:// before sending any request -- no redirect needed.
For first-time visitors or clients that do not support HSTS, the HTTP-to-HTTPS redirect is still necessary. The two mechanisms complement each other:
- HTTP redirect: Handles first-time visitors and non-browser clients (curl, bots, APIs).
- HSTS: Eliminates the redirect for returning browser visitors and prevents SSL stripping attacks.
- HSTS preload: Eliminates the redirect even for first-time visitors by baking the policy into the browser.
With HSTS preloaded, the HTTP-to-HTTPS redirect becomes a fallback for non-browser clients only. But it must still be in place -- HSTS is a browser feature, not a protocol feature.
See the HSTS Preloading guide for deployment details.
Server configuration examples
Nginx: HTTP-to-HTTPS redirect
# Redirect all HTTP to HTTPS with a single 301
# Handles both naked and www, pointing to the canonical URL
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
# Redirect naked HTTPS to www
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://www.example.com$request_uri;
}
# Main server block
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ... application config
}
Note that the HTTP server block redirects directly to https://www.example.com, not to https://example.com. This avoids a two-hop chain (http -> https naked -> https www).
Apache: HTTP-to-HTTPS redirect
# In the port-80 VirtualHost
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
# Single-hop redirect to canonical HTTPS URL
RewriteEngine On
RewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]
</VirtualHost>
# Naked HTTPS to www
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
RewriteEngine On
RewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]
</VirtualHost>
Traefik: HTTP-to-HTTPS redirect
# traefik static configuration
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true # 301, not 302
websecure:
address: ":443"
Traefik's entrypoint-level redirect handles the HTTP-to-HTTPS upgrade globally for all routers. For per-router redirects (e.g., naked to www), use a middleware:
# traefik dynamic configuration
http:
middlewares:
naked-to-www:
redirectRegex:
regex: "^https://example\\.com/(.*)"
replacement: "https://www.example.com/${1}"
permanent: true
Testing redirects
Use curl to follow and display the full redirect chain:
# Show all redirects in the chain
curl -v -L -o /dev/null http://example.com 2>&1 | grep -E "^(< HTTP|< Location)"
# Follow redirects and show each step
curl -sI -L http://example.com
Check for: single-hop redirects (no chains), correct status codes (301 for permanent), HSTS header on the final HTTPS response, and no mixed content warnings.