HTTP Redirects and HTTPS Upgrade

How redirect status codes work, why redirect chains hurt performance and SEO, and how to configure clean HTTP-to-HTTPS upgrades.

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:

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:

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.

Check your site's HTTP headers with spectra