cloudflare / cloudflare/cloudflare-docs
APO follows origin 301/302 redirects internally and drops Set-Cookie, breaking auth flows
- Dominant language
- MDX
- Stars
- 5.2k
- Forks
- 16.7k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 337
Description
## Summary
The Cloudflare edge follows same-origin 301/302 redirects from the origin internally and returns the end-of-chain 200 body to the client. `Set-Cookie` headers set by the first hop are dropped because the internal subrequest to the redirect target does not replay cookies from the prior hop. This breaks any login flow that relies on `Set-Cookie` + `Location` in a single 302 response (e.g., WordPress one-time login tokens, passwordless auth plugins, any SSO handshake that issues 302 after setting an auth cookie).
Behavior is not documented anywhere in the Cloudflare Automatic Platform Optimization (APO), Cache Rules, or Workers docs.
## Scope
- Zone: Pro plan
- WordPress installed under a `/wordpress/` subdirectory (not at root)
- APO installed on zone (`wp_plugin: true`, `wordpress: true`). Setting `enabled: false` and `hostnames: []` via API does **not** remove the APO Worker from the zone routing — responses still carry `cf-apo-via: origin,page-rules` after the toggle.
- Observed on every origin 301/302 to the same hostname, not just auth paths
## Reproduction
Direct curl to the origin IP (bypassing Cloudflare) returns the correct 302 with `Set-Cookie` and `Location`:
```
$ curl -skI https:///wordpress/wp-login.php?wtlwp_token=HASH -H "Host: example.com"
HTTP/2 302
set-cookie: wordpress_sec_...=...
set-cookie: wordpress_logged_in_...=...
cache-control: no-store, no-cache, must-revalidate, private
cf-edge-cache: no-cache
location: https://example.com/wordpress/wp-admin/
```
Through Cloudflare returns 200 with the final-destination body, no auth cookies, no Location:
```
$ curl -sI https://example.com/wordpress/wp-login.php?wtlwp_token=HASH
HTTP/2 200
cf-cache-status: MISS
cf-apo-via: origin,page-rules
cache-control: no-store, must-revalidate, private
[45 KB of login form HTML]
```
Nginx access log for that single client request shows **three** sequential subrequests from the same Cloudflare edge IP, 1 second apart, each chasing the previous `Location` header:
```
162.158.217.84 [11:58:20] GET /wordpress/wp-login.php?wtlwp_token=HASH 302
162.158.217.84 [11:58:21] GET /wordpress/wp-admin/ 302
162.158.217.84 [11:58:22] GET /wordpress/wp-login.php?redirect_to=%2Fwp-admin%2F&reauth=1 200
```
The `Set-Cookie` headers from hop 1 are not replayed on the hop 2 request, so `/wp-admin/` authenticates as unauthenticated and bounces back to the login form.
## Confirmed not the cause (ruled out)
- APO `automatic_platform_optimization` settings: tried `enabled: false`, `wordpress: false`, `wp_plugin: false`, `hostnames: []` — `cf-apo-via` header persists in responses
- Cache Rule with `cache: false` + `browser_ttl: {mode: bypass}` on the affected paths
- Transform Rule forcing `Cache-Control: no-store, no-cache, must-revalidate, private, max-age=0`
- Zone settings: `development_mode: on`, `always_online: off`, `speed_brain: off`, `prefetch_preload: off`, `browser_cache_ttl: 0`
- Workers Routes on the zone (empty)
- Redirect Rules / Page Rules / Config Rules (no match on affected paths)
- Origin headers verified clean via direct IP curl
## What I believe is happening
APO is architected as a Cloudflare Worker that runs on every edge request for the zone ([blog](https://blog.cloudflare.com/building-automatic-platform-optimization-for-wordpress-using-cloudflare-workers/)). That Worker uses `fetch()` subrequests against the origin, and the [default of `fetch()` in Cloudflare Workers is `redirect: "follow"`](https://developers.cloudflare.com/workers/runtime-apis/request/). Each hop is a new subrequest that does not carry cookies from the prior hop's response.
Related known behavior: [cloudflare/miniflare#133](https://github.com/cloudflare/miniflare/issues/133) — Miniflare's fetch internally resolves 302 and strips `Set-Cookie`. Same pattern.
The APO Worker route appears to persist on the zone even after `automatic_platform_optimization.enabled: false` is set via API. The `cf-apo-via` header only appears on zones where APO has been installed.
## What the docs should clarify
1. That APO's Worker intercepts all edge requests for an installed zone and remains installed after toggling `enabled: false` — and how to fully remove it (currently requires a support ticket per community reports).
2. That APO follows origin 301/302 redirects internally and returns the end-of-chain 200 body to the client, dropping intermediate `Set-Cookie` headers — this breaks any auth flow that sets a cookie on a 302 response.
3. That this makes APO incompatible with WordPress plugins that use `wp_set_auth_cookie()` followed by `wp_redirect()` — including the popular "Temporary Login Without Password" plugin, Magic Login plugins, Jetpack SSO, and any passwordless/one-time-token auth flow.
4. Workarounds or how to scope APO to skip admin/login paths.
## References
- https://community.cloudflare.com/t/apo-cache-bypass-with-worker-routes-possible-regression/773362 — confirmed APO + Worker Routes regression, CF staff acknowledged
- https://community.cloudflare.com/t/using-apo-with-workers/213638 — design-level incompatibility
- https://community.cloudflare.com/t/requests-with-a-worker-enabled-always-miss-on-apo-behavior-change/261989 — behavior change confirmed
- https://community.cloudflare.com/t/apo-origin-cookies-always-bypass/533712 — APO + cookie behavior
- https://github.com/cloudflare/miniflare/issues/133 — same runtime pattern in miniflare
Contributor guide
Assessment
This issue has not been assessed yet.