Short answer: Netlify treats a redirect rule with no status code as a permanent 301. Cloudflare Workers static assets treat the exact same _redirects line as a temporary 302. Copy your _redirects file across unchanged, and every rule relying on the default is downgraded to temporary. Nothing errors, pages still load, and browsers still land on the right URL. The difference only shows in the HTTP status code. Fix it by adding an explicit status code (301 or 308) to every rule, then check the live responses with curl -I.
Why do 301s become 302s on Cloudflare?
The two platforms use nearly the same _redirects line format:
[source] [destination] [code?]
The status code is optional on both. The defaults are different (sources checked 2026-10-16):
| Platform / mechanism | Status when none is given | Source (checked 2026-10-16; Cloudflare Workers docs re-checked 2026-10-17) |
|---|---|---|
| Netlify _redirects / netlify.toml | 301 | Netlify: Redirect options (Netlify falls back to 301 when a rule names no code) |
| Cloudflare Workers static assets _redirects | 302 | Cloudflare Workers: Redirects (Cloudflare applies 302 when a rule omits the code) |
| Cloudflare Bulk Redirects | 301 | Cloudflare: URL redirect parameters (status_code, default 301) |
| Cloudflare Workers code | 302 if Response.redirect() gets no status | MDN: Response.redirect() (MDN notes the method sends 302 Found if no status is passed) |
So this Netlify rule:
/old-blog/* /blog/:splat
returns 301 on Netlify and 302 on Cloudflare Workers static assets. Neither platform warns you.
On Cloudflare Workers static assets, a _redirects rule with no status code falls back to 302, as Cloudflare documents (Cloudflare Workers: Redirects), whereas Netlify documents 301 as its fallback for the same situation (Netlify: Redirect options).
In Workers code, Response.redirect() defaults to 302 when you leave the status out; MDN lists 302 Found as the code sent when that argument is missing (MDN: Response.redirect()). Pass 301 (or 308) explicitly as the second argument, as Cloudflare’s redirect example does.
Bulk Redirects default to 301, but set the code explicitly anyway. According to Cloudflare, when the same path appears in both, Bulk Redirects take effect ahead of your Worker, the layer that holds your other redirects (Cloudflare Workers: Redirects), so the Bulk Redirect answers.
How does the downgrade affect SEO?
SEO is where the downgrade matters. From Google Search Central: Redirects and Google Search:
- Permanent (
301,308): Google says its indexing pipeline reads this kind of redirect as a hint that the destination URL should become canonical (Google Search Central: Redirects and Google Search). Google shows the new URL in results, though it may still show the old URL as an alternate name (Google Search Central: Alternate versions of a URL). - Temporary (
302,303,307): Google says its indexing pipeline does not read this kind of redirect as a hint that the destination URL should become canonical (Google Search Central: Redirects and Google Search). Google tends to keep showing the source URL.
After migration, old URLs tell Google the move is temporary, which changes which URL Google treats as canonical. We have no measured ranking or traffic data for this, so we give no number.
How do you detect the downgrade?
1. Spot-check with curl
curl -I doesn’t follow redirects by default, so you see the first hop’s status:
# Old origin (Netlify)
curl -sI https://old.example/old-blog/sample | grep -iE '^(HTTP|location)'
# HTTP/2 301
# location: /blog/sample
# New origin (Cloudflare)
curl -sI https://new.example/old-blog/sample | grep -iE '^(HTTP|location)'
# HTTP/2 302
# location: /blog/sample
The output above is illustrative. Same Location, different status: that’s the bug. To print only the code:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://new.example/old-blog/sample
For wildcard rules, test a concrete path such as /old-blog/sample.
2. Compare origins systematically
Request every known path on both origins without following redirects, and compare:
- the status code of the first response
- the
Locationheader (after normalizing the old host to the new host) - the final status after following redirects
- canonical and robots signals (
<link rel="canonical">,<meta name="robots">,X-Robots-Tag)
Build the path list from your _redirects file (one concrete URL per rule), your sitemap, and a short crawl.
Severity rules:
- Error: a permanent redirect (
301/308) becomes non-permanent (302/303/307). - Error: a redirect disappears (for example
301→200), or a200becomes a redirect. - Error: a canonical change, or a robots change that involves
noindex. - Warning: other redirect-code changes (
302→301,301→308).
3. Gate deploys on it
Run the parity check in CI against the old and new origins before you switch DNS. Fail on errors and keep the report.
How do you fix it?
Step 1 — Make every status explicit. Before moving the file, add 301 to every Netlify rule that has no code:
# before
/old-blog/* /blog/:splat
# after
/old-blog/* /blog/:splat 301
To find rules with no code, list non-comment lines whose last field isn’t a number, and check netlify.toml [[redirects]] blocks that have no status = line.
Step 2 — Handle Netlify-only syntax. On Workers static assets, _redirects does not support query-parameter matching, domain-level redirects, or redirects by country, language, or cookie. Of the non-redirect codes, it supports only 200 proxying, and only to relative URLs on your own site (no external domains). Rewrite these rules as Bulk Redirects, Redirect Rules, or Worker code. Netlify’s ! suffix (for example 301!) forces a rule to apply even when a file exists at that path (Netlify: Rewrites and proxies). Cloudflare’s Workers static assets docs don’t list this suffix, and it isn’t needed: Cloudflare states that its static assets redirects always apply, whether or not a file matches the requested path (Cloudflare Workers: Redirects). Drop the !, keep the code, and test those paths.
Step 3 — Watch the limits. Cloudflare caps a single Workers static assets _redirects file at 2,000 static rules and 100 dynamic ones, 2,100 in all, and allows each rule up to 1,000 characters (Cloudflare Workers: Redirects). Past that ceiling, move the extra rules to Bulk Redirects.
Step 4 — Worker code bypasses _redirects. Cloudflare’s Workers docs say _redirects rules are skipped for any request your Worker code serves, even when its URL fits one of those rules (Cloudflare Workers: Redirects). Check any routes your Worker answers first, such as patterns listed in assets.run_worker_first. Move those redirects into the Worker and pass the status to Response.redirect() explicitly.
Step 5 — Re-verify. Re-run the checks on the new origin before and after cutover.
What belongs on the migration checklist?
- Export all Netlify rules from
_redirectsandnetlify.toml. - Add an explicit status code to every rule (
301/308for permanent moves). - Flag rules using query params, conditions (
Country,Language,Cookie, or Netlify’sRole), or domain-level sources and move them to Bulk Redirects, Redirect Rules, or Workers. - Remove the
!force suffix from301!-style rules and test those paths. - Check counts against the 2,000 static / 100 dynamic limit.
- Check which routes your Worker code serves (for example,
assets.run_worker_firstpatterns), since those bypass_redirects. - In Workers, always pass an explicit status to
Response.redirect(). - Compare origins: status,
Location, final status, canonical, robots. - Treat any permanent → temporary change as a blocking error.
- Gate the DNS switch on a clean report, and keep the report.
What else do people ask?
Why did my 301 redirects turn into 302s after moving to Cloudflare?
Netlify uses 301 when a rule has no status code, and Cloudflare Workers static assets use 302. Copying the _redirects file without explicit codes downgrades every such rule.
Does a 302 instead of a 301 hurt SEO?
It changes the signal Google gets. Google’s documentation says a permanent redirect tells Google that the target should be canonical, and a temporary one does not. Google may keep showing the old URL.
Do Cloudflare Bulk Redirects have the same problem?
Not by default: Bulk Redirects use 301 when no status code is set, but _redirects on Workers static assets uses 302. Set the code explicitly in both.
How can I check a redirect’s status code quickly?
Run curl -sI <url> and read the first line, or curl -s -o /dev/null -w '%{http_code}\n' <url>. Don’t use -L, which follows the redirect and hides the first hop.
Should I use 301 or 308?
Google treats both as permanent. 308 keeps the HTTP method (for example POST), while with 301 clients may switch to GET (RFC 9110 §15.4; Cloudflare: URL redirect parameters). Browsers may cache 301/308 heuristically. A 302 gets no heuristic caching: a browser or CDN can reuse a stored copy only if the response states how long it stays fresh, through Cache-Control: max-age or Expires, or carries a directive that explicitly permits storing it, such as Cache-Control: public (RFC 9111 §4.2.2). For ordinary page moves, either works.
Make every status code explicit, and check the first hop before you switch DNS.
Sources
Sources checked 2026-10-16; Cloudflare Workers docs re-checked 2026-10-17.
- Netlify Docs, Redirect options: https://docs.netlify.com/manage/routing/redirects/redirect-options/
- Netlify Docs, Rewrites and proxies: https://docs.netlify.com/manage/routing/redirects/rewrites-proxies/
- Cloudflare Rules Docs, Single Redirects: https://developers.cloudflare.com/rules/url-forwarding/single-redirects/
- Definitions: HTTP (RFC 9110) https://www.rfc-editor.org/rfc/rfc9110; URL (RFC 3986) https://www.rfc-editor.org/rfc/rfc3986; DNS (Cloudflare Learning) https://www.cloudflare.com/learning/dns/what-is-dns/
- Cloudflare Rules Docs, URL redirect parameters (Bulk Redirects): https://developers.cloudflare.com/rules/url-forwarding/bulk-redirects/reference/parameters/
- Cloudflare Workers Docs, Static assets redirects: https://developers.cloudflare.com/workers/static-assets/redirects/
- Cloudflare Workers Docs, Static assets binding (run_worker_first): https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first
- Cloudflare Workers Docs, Redirect example: https://developers.cloudflare.com/workers/examples/redirect/
- Google Search Central, Redirects and Google Search: https://developers.google.com/search/docs/crawling-indexing/301-redirects
- Google Search Central Blog, Changes to HowTo and FAQ rich results (2023-08-08): https://developers.google.com/search/blog/2023/08/howto-faq-changes
- RFC 9110 §15.1 Overview of status codes (heuristically cacheable codes): https://www.rfc-editor.org/rfc/rfc9110#section-15.1
- RFC 9110 §15.4 Redirection 3xx: https://www.rfc-editor.org/rfc/rfc9110#section-15.4
- MDN, Response.redirect(): https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static
- MDN, HTTP caching (Heuristic caching): https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching#heuristic_caching