CORS

Why Your Cloudflare CORS Still Fails โ€” And How to Fix It

Updated April 2026

Reading this? Verify your fix live. Test your CORS config โ†’ CORSFixer

You added CORS headers to your server. They work when you hit the origin directly. They fail through Cloudflare. These are the four Cloudflare-specific CORS problems and their exact fixes.

Problem 1 โ€” Cloudflare is caching a stale response without CORS headers

# Symptom: CORS works in curl to origin, fails in browser through Cloudflare
# Cause: Cloudflare cached the response before CORS headers were added

# Fix: Purge Cloudflare cache
# Dashboard โ†’ Caching โ†’ Configuration โ†’ Purge Cache โ†’ Purge Everything

# Or purge specific URLs via API
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" --data '{"purge_everything": true}'

Problem 2 โ€” Cloudflare is caching OPTIONS preflight responses

# Symptom: OPTIONS returns correct CORS headers once, then cached incorrectly
# Fix: Tell Cloudflare not to cache preflight responses

# On your origin server โ€” add to OPTIONS responses:
Cache-Control: no-store

# Or in Cloudflare โ€” create a Cache Rule:
# Rules โ†’ Cache Rules โ†’ Create Rule
# If: Request method equals "OPTIONS"
# Then: Bypass cache

Problem 3 โ€” Duplicate CORS headers (origin + Transform Rule)

# Symptom: "The 'Access-Control-Allow-Origin' header contains multiple values"
# Cause: Both your origin server AND Cloudflare Transform Rule add the header

# Fix Option A: Remove from origin, add only via Transform Rule
# Transform Rules โ†’ Modify Response Headers โ†’ Set (overwrite) not Add

# Fix Option B: Remove from Transform Rule, handle only at origin
# Dashboard โ†’ Rules โ†’ Transform Rules โ†’ delete the CORS rule

Problem 4 โ€” Cloudflare stripping custom headers

# Symptom: CORS headers from your origin disappear when proxied through Cloudflare
# This is uncommon but can happen with certain header names

# Check: Does your header appear in curl to origin?
curl -I https://your-origin-ip.com/ -H "Host: yourapp.com"

# If yes but not through Cloudflare: create a Transform Rule to re-add them
# Rules โ†’ Transform Rules โ†’ Modify Response Headers
# Action: Set | Header name: Access-Control-Allow-Origin | Value: https://yourapp.com

Adding CORS via Cloudflare Transform Rules (the clean approach)

# Dashboard โ†’ Rules โ†’ Transform Rules โ†’ Modify Response Headers โ†’ Create Rule

# Rule name: CORS Headers
# When: Hostname equals yourapp.com
# Then:
# Set Access-Control-Allow-Origin = https://yourfrontend.com
# Set Access-Control-Allow-Methods = GET, POST, PUT, DELETE, OPTIONS
# Set Access-Control-Allow-Headers = Authorization, Content-Type

# Note: Set overwrites; Add appends. Use Set to avoid duplicates.

Handle OPTIONS preflight at Cloudflare Worker (most reliable)

// workers.js โ€” handles CORS before request reaches your origin
export default { async fetch(request, env) { const origin = request.headers.get("Origin"); const ALLOWED = ["https://yourapp.com"]; const allowedOrigin = ALLOWED.includes(origin) ? origin : ""; if (request.method === "OPTIONS") { return new Response(null, { status: 204, headers: { "Access-Control-Allow-Origin": allowedOrigin, "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Authorization, Content-Type", "Access-Control-Max-Age": "86400", "Cache-Control": "no-store", }, }); } const response = await fetch(request); const newResponse = new Response(response.body, response); newResponse.headers.set("Access-Control-Allow-Origin", allowedOrigin); return newResponse; },
};
Test your CORS config โ†’ CORSFixer