When to Use 302 Redirects During Phased Migrations

Problem Statement

Phased migrations route traffic in stages while content parity, infrastructure, and indexation are still being verified. The risk is that a permanent 301 deployed during staging, an A/B test, or an incremental rollout tells search engines the move is final before the new URL is actually validated — the original URL is dropped from the index, canonical signals conflict, and crawl budget is wasted re-evaluating an unstable target. The correct tool for a deliberately reversible state is a temporary 302, paired with strict cache headers so edge networks never persist the temporary route. This page sits under the 301 vs 302 Decision Trees section and isolates temporary routing from permanent equity transfer.

302 versus 301 decision tree for phased migrations A decision tree routing a migration phase to a 302 when the move is reversible and to a 301 once content parity and canonical tags are verified. 302 vs 301 in a Phased Move Is the move final? Use 302 (temporary) Use 301 (permanent) parity + canonicals? staging / A/B / rollout add no-store headers parity verified equity transfers No Yes Promote 302 to 301 once the phase is confirmed
A 302 holds the reversible state; once parity and canonicals are verified, the same route is promoted to a 301.

When to Use This Approach

  • The new URL is not yet at full content parity with the legacy URL and may be rolled back within the phase window.
  • You are running an A/B or canary test and want the original URL to retain its index position for the duration.
  • Staging or pre-production environments are being routed to from a shared host and must not leak into the index.
  • A maintenance window or geo-targeted experiment needs traffic diverted temporarily without signalling a permanent move.
  • You expect to promote the route to a permanent 301 within weeks, not months — a 302 that outlives its phase becomes a liability.

Step-by-Step Instructions

1. Generate Temporary Rules From Your Mapping CSV

Drive the 302 set from the same source of truth as your permanent rules so phases stay synchronised with the master CSV Mapping Workflows schema. Generate Apache rules directly from a two-column CSV.

# Generate 302 rules from a CSV with columns: old_path,new_path
awk -F',' 'NR>1 {
    gsub(/\//, "\\/", $1)                    # escape slashes in the source path
    print "RewriteRule ^" $1 "$ " $2 " [R=302,L]"
}' legacy_urls.csv > temp_redirects.conf

Generate the temporary rules from the same inventory as the permanent ones, marked with their phase and their intended promotion date. Keeping them in a separate hand-maintained file feels tidier and reliably produces an estate where nobody can answer which routes are still provisional — and a provisional route nobody remembers is one that never gets promoted. One inventory, a status column, and a phase column is all it takes to make the question answerable by a query.

2. Deploy the 302 With Anti-Cache Headers

Edge networks and browsers will cache a 302 unless told not to. Always set Cache-Control: no-store on the temporary route so the move stays reversible.

# Nginx — temporary route, never cached at the edge
location ~ ^/old-path/(.*)$ {
    return 302 /new-path/$1;
    add_header Cache-Control "no-store, max-age=0";   # prevent edge-cache poisoning
}
# Apache .htaccess — NE keeps already-encoded characters intact
RewriteEngine On
RewriteRule ^old-path/(.*)$ /new-path/$1 [R=302,L,NE]
Header always set Cache-Control "no-cache, no-store, must-revalidate"

3. Mirror the Rule at the Edge When Needed

If a CDN sits in front of origin, replicate the 302 at the edge so the temporary route is consistent everywhere. The current Cloudflare Workers ES-modules syntax is below (the legacy addEventListener('fetch', ...) form is deprecated).

// Cloudflare Worker — temporary edge redirect, query string preserved
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname.startsWith('/old-path/')) {
      const newPath = url.pathname.replace('/old-path/', '/new-path/');
      return Response.redirect(url.origin + newPath + url.search, 302);
    }
    return fetch(request);
  }
}

Mirror at the edge only where the edge is answering for those paths anyway. A phased rollout that adds edge rules purely to make the shift faster acquires a second place where the routing lives, and therefore a second place to remember during promotion and rollback. Where the edge already owns the path, put the rule there and keep the origin out of it; where it does not, resist adding it for the duration of a phase.

4. Promote to 301 When the Phase Is Confirmed

Once content parity holds and canonical tags point at the new URL, switch the status code to lock in equity transfer. Deploy during a low-traffic window and resubmit the URL for inspection.

# Same rule, promoted to permanent once the phase is verified
RewriteRule ^old-path/(.*)$ /new-path/$1 [R=301,L,NE]
Header always set Cache-Control "public, max-age=86400"
Promotion gate from 302 to 301 Four conditions that must all hold before a temporary redirect is promoted to permanent: destination parity verified, error rate within budget, the destination crawled, and the phase signed off. Promotion is a milestone with a gate, not a tidy-up Condition Evidence Owner Destination parity verified template diff against baseline QA Error budget held 72 h 5xx below threshold, sustained SRE Destination crawled Search Console URL Inspection SEO Phase signed off recorded, with a name Launch lead Promote a whole segment in one controlled deploy — piecemeal promotion leaves a mixed estate nobody can reason about during an incident.
Without a gate, a 302 that was meant to be provisional becomes permanent by neglect, and the destination never consolidates.

Worked Example

A retailer migrating shop.example.com runs the new product templates behind a 10% canary for two weeks. During the canary, /old-path/blue-widget returns a 302 to /new-path/blue-widget:

GET /old-path/blue-widget HTTP/1.1
Host: shop.example.com

HTTP/1.1 302 Found
Location: https://shop.example.com/new-path/blue-widget
Cache-Control: no-store, max-age=0

Because the response is 302 Found with no-store, Google keeps /old-path/blue-widget indexed and the CDN never persists the hop — if the canary regresses, removing the rule instantly restores the original behaviour. After two weeks of clean parity checks the team flips the rule to R=301, the response becomes 301 Moved Permanently, and index consolidation begins. Flatten any intermediate hops introduced by the phase using Redirect Chain Elimination before the 301 goes live.

Cache behaviour of a temporary redirect How browsers, CDNs and search engines each treat a 302 during a phased rollout, and why short cache headers matter for the eventual promotion. Keep it cheap to change your mind Consumer Treats a 302 as On promotion Browser re-check next time picks up the 301 promptly CDN edge cacheable unless told otherwise needs a purge Search engine source stays canonical destination consolidates Set a short Cache-Control on the 302 phase so the promotion propagates quickly rather than waiting out an edge TTL you chose by accident.
The middle row is the one that bites: a 302 cached at the edge for hours makes the promotion feel like it has not worked.

Verify the status code explicitly rather than the destination. A check that confirms where a URL lands passes identically on a 301 and a 302, and the distinction between those is the entire subject of a phased rollout — so an assertion that omits it will happily report success on a route that was promoted early or never promoted at all. Assert on both the code and the target, and run the check across the whole phase rather than on a sample.

Verification

Confirm the response is a single-hop 302 with no caching, then re-verify after the promotion to 301. If indexation degrades during the phase, replace return 302 with return 410 (Gone) to halt crawler traversal, purge the CDN via the provider API, and fall through to normal routing.

# Confirm status, single hop, and final target
curl -sI -L -o /dev/null \
  -w "%{http_code} %{num_redirects} hops -> %{redirect_url}\n" \
  https://shop.example.com/old-path/blue-widget
# Expect: 302 1 hops -> https://shop.example.com/new-path/blue-widget

# Confirm the edge is not caching the temporary route
curl -sI https://shop.example.com/old-path/blue-widget | grep -i cache-control
# Expect: cache-control: no-store, max-age=0

FAQ

How do I stop a CDN from permanently caching a 302 during a phased migration? Set Cache-Control: no-store, max-age=0, must-revalidate and Pragma: no-cache on every 302 response, and add a CDN cache rule that bypasses caching for the matching paths. Validate with curl -sI https://domain.com/path | grep -i cache-control.

How do I safely convert a 302 to a 301 without triggering re-evaluation penalties? Verify content parity and canonical tags first, change the rule to R=301, deploy during a low-traffic window, then submit the URL via Google Search Console URL Inspection. Monitor the Indexing report for 72 hours before removing the old sitemap entry.

Can I preserve query parameters in CSV-generated 302 rules? Yes. In Apache use RewriteRule ^old/(.*)$ /new/$1 [R=302,L,QSA]; in Nginx use return 302 /new/$1$is_args$args;. Strip leading slashes from the CSV old_path column so the generated rule does not produce a double slash.

What if the phase never gets promoted? Then you have shipped a permanent temporary redirect, which is the worst of both outcomes — the destination cannot consolidate because search engines keep ranking the source, and the source cannot be retired because it is still the canonical URL. This is the normal failure mode rather than an unusual one, because promotion is nobody’s urgent task once the phase is working. Put a dated ticket against it at the moment you deploy the 302, with the gate conditions written into the ticket, and treat the migration as incomplete until it closes.

Can I use 307 instead of 302 for a phased rollout? Only where the route carries a request body. 307 preserves the method as well as signalling temporariness, which matters for a form endpoint or an API in a staged cutover and buys nothing for an ordinary page. Since 307 has slightly patchier support among older intermediaries than 302, use it deliberately for the small set of routes that need it rather than as a blanket replacement.

How long is too long for a phase to stay on 302? Longer than a few weeks and the reason is usually organisational rather than technical — the gate conditions were met and nobody owned the promotion. Search engines do eventually treat a persistent temporary redirect as permanent, but that behaviour is heuristic, undocumented and not something to design around: relying on it means accepting an indefinite period in which the destination cannot consolidate. Set a promotion date when you deploy the phase and treat overrunning it as a defect rather than as a state.

Should the 302 phase use a shorter cache lifetime than normal? Yes, deliberately. The whole point of the phase is that you may change your mind, and a 302 cached at the edge for hours makes both a reversal and a promotion feel as though they have not worked. A short Cache-Control on the redirect response costs a little edge efficiency for the duration of the phase and buys you the ability to act on the gate the moment it opens.

Related

← Back to 301 vs 302 Decision Trees