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://<origin-ip>/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). That Worker uses fetch() subrequests against the origin, and the default of fetch() in Cloudflare Workers is redirect: "follow". Each hop is a new subrequest that does not carry cookies from the prior hop's response.
Related known behavior: cloudflare/miniflare#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
- 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).
- 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.
- 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.
- Workarounds or how to scope APO to skip admin/login paths.
References
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-Cookieheaders 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 onSet-Cookie+Locationin 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
/wordpress/subdirectory (not at root)wp_plugin: true,wordpress: true). Settingenabled: falseandhostnames: []via API does not remove the APO Worker from the zone routing — responses still carrycf-apo-via: origin,page-rulesafter the toggle.Reproduction
Direct curl to the origin IP (bypassing Cloudflare) returns the correct 302 with
Set-CookieandLocation:Through Cloudflare returns 200 with the final-destination body, no auth cookies, no Location:
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
Locationheader:The
Set-Cookieheaders 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)
automatic_platform_optimizationsettings: triedenabled: false,wordpress: false,wp_plugin: false,hostnames: []—cf-apo-viaheader persists in responsescache: false+browser_ttl: {mode: bypass}on the affected pathsCache-Control: no-store, no-cache, must-revalidate, private, max-age=0development_mode: on,always_online: off,speed_brain: off,prefetch_preload: off,browser_cache_ttl: 0What I believe is happening
APO is architected as a Cloudflare Worker that runs on every edge request for the zone (blog). That Worker uses
fetch()subrequests against the origin, and the default offetch()in Cloudflare Workers isredirect: "follow". Each hop is a new subrequest that does not carry cookies from the prior hop's response.Related known behavior: cloudflare/miniflare#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: falseis set via API. Thecf-apo-viaheader only appears on zones where APO has been installed.What the docs should clarify
enabled: false— and how to fully remove it (currently requires a support ticket per community reports).Set-Cookieheaders — this breaks any auth flow that sets a cookie on a 302 response.wp_set_auth_cookie()followed bywp_redirect()— including the popular "Temporary Login Without Password" plugin, Magic Login plugins, Jetpack SSO, and any passwordless/one-time-token auth flow.References