HTTP Redirections

HTTP redirections instruct a client to fetch a resource from a different URL. The server includes a Location header in the response, and the client follows the new address automatically. Redirections handle domain migrations, HTTPS enforcement, URL restructuring, and load distribution.

How redirections work

A redirection response carries a 3xx status code and a Location header pointing to the target resource. When a browser or crawler receives this response, a second request goes out to the URL specified in Location.

HTTP/1.1 301 Moved Permanently
Location: https://example.re/new-page

The original response body is typically empty or contains a short HTML document with a link to the new location. Clients following the redirect discard this body and load the target URL.

Permanent redirections

A permanent redirect signals the resource has moved to a new URL for good. Clients and caches store the new URL and stop requesting the old one.

301 Moved Permanently

301 is the most widely used permanent redirect. The response is cacheable by default. Clients are permitted to change the request method from POST to GET when following a 301 redirect, and most browsers do exactly this.

308 Permanent Redirect

308 works like 301 but preserves the original request method. This status code was introduced to fill a gap: there was no permanent redirect guaranteeing method preservation. 308 is now part of the core HTTP semantics. A POST request redirected with 308 remains a POST at the target URL.

Temporary redirections

A temporary redirect signals the resource is available at a different URL for now, but the original URL remains valid. Clients continue to request the original URL in the future.

302 Found

302 is the original temporary redirect. The HTTP/1.0 specification did not clearly define whether clients must preserve the request method. In practice, most clients change POST to GET when following a 302. To remove this ambiguity, 303 and 307 were introduced.

303 See Other

303 tells the client to retrieve the redirect target using GET, regardless of the original request method. A common use case is redirecting after a form submission: the server processes the POST, then responds with 303 pointing to a confirmation page the client fetches with GET.

307 Temporary Redirect

307 works like 302 but guarantees method preservation. A POST stays a POST at the redirect target. 307 exists because clients routinely changed the method on 302 responses, creating a need for an unambiguous temporary redirect.

Permanent vs temporary redirects

The choice between a permanent and a temporary redirect is a statement about the original address, not about how long the redirect stays in place. A permanent redirect says the old address is finished and the new one takes over. A temporary redirect says the old address remains the real home and the detour ends at some point.

Consequences follow from the statement rather than from the code number. Permanent redirects transfer ranking signals to the target and let search engines replace the old address in the index. Temporary redirects keep the original address indexed, so signals stay where they were.

Caching behaves the same way. 301 and 308 responses are cacheable by default, and a client storing one stops asking for the old address at all, which makes an incorrect permanent redirect expensive to undo. 302 and 307 are not cacheable by default, so a change of mind takes effect on the next request.

Intent Preserves method Code
Permanent No 301
Permanent Yes 308
Temporary No 302
Temporary Yes 307

Site moves, protocol upgrades, and canonical address changes are permanent. Maintenance pages, A/B tests, geographic routing, and login detours are temporary, because the original address stays in service once the condition passes.

Method preservation matrix

Status Type Method preserved Cacheable by default
301 Permanent No (POST to GET) Yes
308 Permanent Yes Yes
302 Temporary No (POST to GET) No
303 Temporary No (always GET) No
307 Temporary Yes No

The specification and browser behavior differ here, and the gap is worth knowing. HTTP permits a client to keep the original method on 301 and 302, and describes the conversion to GET as historical rather than required. Browsers converged on converting anyway, and the behavior is now mandated for them: a redirected POST becomes a GET with the body discarded and the body-related headers stripped.

In practice 301 and 302 always downgrade a POST in a browser, while non-browser clients following the specification to the letter sometimes keep the method. Depending on either behavior is what 307 and 308 exist to avoid.

Both codes originally preserved the method when defined, and early clients split over whether to honor the rule on POST. Prevailing practice settled on the conversion, and 307 and 308 arrived later to state the preserving intent without ambiguity.

Redirects crossing an origin boundary also lose credentials. Browsers strip the Authorization header the moment a redirect chain reaches a different origin, so an authenticated request following a cross-origin redirect arrives without its credentials.

Other 3xx status codes

300 Multiple Choices

300 indicates multiple representations exist for the requested resource. The server provides a list of options and the client selects the most suitable one. This status code is rarely used in practice.

304 Not Modified

304 is technically a 3xx status code, but does not function as a redirect in the forwarding sense. A 304 response tells the client the cached version of the resource is still current. No Location header is involved. The Conditional-Requests article covers 304 in detail.

305 Use Proxy and 306

Common redirect scenarios

HTTP to HTTPS

Enforcing HTTPS is the most common redirect pattern. The server responds to plaintext HTTP requests with a 301 pointing to the secure version of the same URL.

HTTP/1.1 301 Moved Permanently
Location: https://example.re/page

Combining this redirect with Strict-Transport-Security (HSTS) eliminates subsequent plaintext requests entirely once the browser records the HSTS policy.

Domain consolidation

Organizations consolidate multiple domain names to a single canonical domain. A 301 from www.example.re to example.re (or vice versa) ensures search engines index one version and link equity concentrates on a single domain.

Path changes and site restructuring

When URL paths change during a site migration, a mapping of old paths to new paths through 301 or 308 redirects prevents broken links and preserves search rankings.

Redirect 301 /old-page https://example.re/new-page

Client-side redirect alternatives

Server-side HTTP redirects are the most reliable approach. When server configuration is not accessible, two client-side methods exist.

Meta refresh

An HTML meta element with http-equiv="refresh" triggers a client-side redirect. The content attribute specifies the delay in seconds and the target URL.

<meta http-equiv="refresh"
  content="0; url=https://example.re/">

A zero-second delay acts as an instant redirect. Google treats a zero-second meta refresh as a permanent redirect signal. A nonzero delay is treated as a temporary redirect.

JavaScript redirect

Setting window.location in JavaScript redirects the browser to a new URL.

<script>
  window.location.href = "https://example.re";
</script>

JavaScript redirects depend on the client executing scripts. Search engine crawlers render JavaScript for indexing purposes, but rendering adds latency and is less reliable than server-side redirects. Google treats JavaScript redirects as permanent redirect signals.

Soft 404 pattern

A page returning 200 with content stating the resource moved to a new URL is sometimes called a "soft redirect" or soft 404. This pattern provides no machine-readable redirect signal. Clients and crawlers treat the response as a normal 200. Search engines see the old URL as a live page with thin content rather than a proper redirect.

Redirect precedence

When multiple redirect mechanisms exist on a single page, HTTP-level redirects take highest priority because the client processes the status code before parsing the response body. Meta refresh redirects take second priority. JavaScript redirects execute last, only after the script engine runs.

SEO implications

Search engines treat permanent and temporary redirects differently during indexing.

Permanent redirects (301, 308) send a strong signal to search engines indicating the target URL is the Canonical version. Google transfers indexing signals to the target and replaces the old URL in search results.

Temporary redirects (302, 303, 307) send a weak signal. Search engines keep the original URL in the index and continue crawling the source URL, expecting the redirect to end.

Redirect chains

A redirect chain occurs when URL A redirects to URL B, which redirects to URL C. Each hop adds network latency and consumes crawl budget. Googlebot follows up to 10 redirect hops before abandoning the chain. Best practice is to keep chains under three hops, ideally redirecting directly from the original URL to the final destination.

Redirect loops

A misconfigured server or corrupted client cache creates circular redirects where URL A redirects to URL B and URL B redirects back to URL A. Browsers stop after twenty redirects and report an error, a limit Chrome and Firefox both apply. Servers sometimes catch the loop first and return 500 Internal Server Error.

Search crawlers stop far sooner. Googlebot follows up to ten hops in a chain, and Google recommends staying under five, ideally no more than three. A chain inside the browser limit still costs indexing once the chain passes the crawler limit, leaving the final address unseen.

Chains form by accumulation rather than by design. Separate rules for HTTP to HTTPS, www to naked domain, trailing slashes, and legacy paths each add a hop, and four independent rules produce a four-hop chain for a single request. Collapsing them into one rule pointing at the final address removes the latency and the crawler exposure together.

Common causes include conflicting rewrite rules, CMS misconfiguration, and stale Cookies forcing repeated redirects. A TLS-terminating proxy passing plain HTTP to an origin redirecting all HTTP to HTTPS produces the most persistent version, since each side behaves correctly alone and the loop exists only in combination.

Redirect examples

nginx

The return directive answers with a status code and a location in one step. Codes 301, 302, 303, 307, and 308 all accept a redirect target.

Whole host moved, path and query preserved

server {
    listen 80;
    listen [::]:80;
    server_name old.example.re;
    return 301 https://new.example.re$request_uri;
}

HTTP to HTTPS

server {
    listen 80;
    listen [::]:80;
    server_name example.re www.example.re;
    return 301 https://$host$request_uri;
}

$request_uri carries the original path together with the query string, which is what keeps a redirect from discarding parameters.

The rewrite directive handles patterns needing capture, and answers with 301 through the permanent flag or 302 through redirect. Those two codes are the only ones rewrite produces, so a method-preserving redirect needs return with a regular expression location.

Method-preserving redirect for an API path

location ~ ^/api/v1/(?<rest>.*)$ {
    return 308 /api/v2/$rest$is_args$args;
}

Version history matters on the newer codes. nginx treated 307 as a redirect from 1.1.16 and 1.0.13 onward, and 308 only from 1.13.0. Older builds answer with the status and leave the location unused.

The NGINX blog recommends return over rewrite wherever both fit. rewrite evaluates a regular expression on every matching request, while return 301 states the outcome plainly. Reserve rewrite for captures and path manipulation beyond the reach of built-in variables.

Apache

Apache documents mod_alias as the tool for straightforward redirects and mod_rewrite for anything needing query string manipulation. Reaching for mod_rewrite first produces configurations harder to follow than the task warrants.

Single path, permanent

Redirect permanent "/old-page.html" "/new-page.html"

Temporary redirect

Redirect 302 "/promo" "/campaigns/summer"

Pattern match with capture

RedirectMatch permanent "^/blog/([0-9]{4})/(.*)$" "https://example.re/archive/$1/$2"

The permanent, temp, seeother, and gone keywords cover 301, 302, 303, and 410. Method preserving codes take a number instead, since no keyword exists for either.

Redirect 307 "/api/submit" "https://api.example.re/submit"
Redirect 308 "/api/v1" "https://api.example.re/v2"

Apache answers only with codes the build recognizes, and 308 arrived in 2.4.3. Older builds respond 500 rather than redirecting, which makes the failure look like a configuration error rather than a missing feature.

A Redirect matches complete path segments only, and appends any trailing path to the target. Query strings survive the hop.

Request bodies are a different matter, and Apache documents the discarding of a POST plainly. The cause sits with the default status rather than with the directive: Redirect answers 302 unless told otherwise, and the client converts the POST to a GET on its own. Naming 307 or 308 explicitly preserves both method and body, so the discarding is a default worth changing rather than a limitation to work around.

mod_rewrite covers the rest. The R flag sets the code and pairs with L in nearly every case, because R alone hands the rewritten address to the next rule.

RewriteEngine On
RewriteRule "^/docs/(.*)" "https://docs.example.re/$1" [R=301,L]

Query string handling has three controls. QSA merges the original query with a new one, QSD discards the original outright, and a bare question mark at the end of the substitution clears the query string.

RewriteRule "^/pages/(.+)" "/page.php?page=$1" [QSA,R=301,L]
RewriteRule "^/search" "/find?" [R=301,L]

HTTP to HTTPS, with virtual host access

<VirtualHost *:80>
    ServerName www.example.re
    Redirect permanent "/" "https://www.example.re/"
</VirtualHost>

HTTP to HTTPS inside .htaccess

RewriteEngine On
RewriteCond "%{HTTPS}" !=on
RewriteRule "^(.*)$" "https://%{HTTP_HOST}%{REQUEST_URI}" [R=301,L]

REQUEST_URI arrives percent-decoded, and mod_rewrite re-encodes the output again on an external redirect. The round trip is where the trouble starts: the percent sign itself gets escaped to %25, so encoding already present in the path comes out doubled, turning %20 into %2520. The NE flag suppresses the re-encoding for rules handling already-encoded paths.

Encoded slashes follow a separate rule. AllowEncodedSlashes defaults to off and answers a request containing %2F with 404 before any rewrite rule runs, so such a path never reaches the redirect. Serving them needs the directive set to NoDecode.

Rules living in a directory context restart from the top after L, which turns a redirect into a loop. The END flag stops the pass outright and avoids the repeat.

Cloudflare

Two dedicated products cover redirects at the edge, with Snippets available for logic neither expresses. The choice between the two depends on scope. Single Redirects work at the zone level and support wildcard patterns, with regular expressions available through Cloudflare configuration. Bulk Redirects work at the account level across domains and hold static lists rather than patterns. Both need the domain proxied rather than DNS-only.

Rule counts and list capacity vary by Cloudflare account, so check the current limits in the dashboard rather than assuming a fixed number.

Both products accept 301, 302, 307, and 308, and default to 301.

The query string is the trap. Both products discard the query by default, where Apache passes one through and nginx keeps one through $request_uri. A migration moving from a server-level redirect to a Cloudflare rule silently drops campaign parameters and session tokens unless the preserve option is switched on.

The preserve option carries its own surprise. Turning the setting on makes the redirect use the incoming query string and discard any query written into the target, even where the original request carried none. Preserving the incoming query and appending a fixed parameter are mutually exclusive.

"action_parameters": {
  "from_value": {
    "target_url": { "value": "https://example.re/new" },
    "status_code": 301,
    "preserve_query_string": true
  }
}

Order matters when both products are active. Single Redirects evaluate before Bulk Redirects, so a zone-level rule takes effect ahead of an account-level list covering the same address.

PHP

<?php
header("Location: https://example.re/docs", true, 301);
exit();
?>

The third argument sets the status code. The exit call matters because output after a header call continues executing and reaches a client already told to leave.

See also

Last updated: August 18, 2026