Cross-Origin Resource Sharing (CORS) preflight requests are a silent performance tax on modern web applications. Every time a browser issues a non-simple cross-origin request — those with custom headers, credentials, or methods beyond GET and POST — it first sends an HTTP OPTIONS request to verify permissions. When your API lives behind a CDN, each of these preflights can add 50–150ms of latency if it must travel back to origin. At scale, with single-page applications making dozens of API calls per page load, this overhead compounds into seconds of delay, directly impacting Core Web Vitals and conversion rates.
This guide details how to optimize CORS preflight handling at the CDN edge by caching OPTIONS responses, tuning headers, and eliminating unnecessary round trips — without compromising security. You will learn the exact cache key design, header configuration, and edge logic required to serve preflights in under 10ms.
1. What Triggers a Preflight — and Why CDNs Struggle With It
A preflight is triggered when a request is not simple per the Fetch specification. Simple requests are limited to GET, HEAD, or POST with only CORS-safelisted headers (Accept, Accept-Language, Content-Language, Content-Type with values text/plain, multipart/form-data, or application/x-www-form-urlencoded). Everything else triggers an OPTIONS check.
In practice, nearly all modern API traffic triggers preflights:
Content-Type: application/json— the most common API content type — is not safelistedAuthorization: Bearer <token>or anyX-*custom header- Methods like
PUT,PATCH,DELETE - Requests with
credentials: 'include'orwithCredentials: true
The browser sends an OPTIONS request with Origin, Access-Control-Request-Method, and Access-Control-Request-Headers. The server must respond with Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers before the real request is sent. By default, most CDNs treat OPTIONS as uncacheable and proxy it to origin every time. That means even if your GET responses are cached at the edge in 15ms, the preflight still takes a full origin round trip of 80ms or more. For applications making 10–20 API calls on load, you are paying this penalty repeatedly. Understanding how CDN caching benefits performance and security helps frame why preflight optimization is a critical extension of edge strategy.
2. The Real Performance Cost of Uncached OPTIONS
We measured preflight overhead across three production e-commerce APIs before and after edge caching. The results are consistent: uncached preflights dominate API latency.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
On a typical US East to US West origin path, a cached GET from edge averages 18ms. The preceding OPTIONS, when forwarded to origin, averages 92ms. The browser blocks the actual GET until the OPTIONS completes, so the effective latency for that resource becomes 110ms — a 6x increase. For sequential API dependencies, this is additive. A checkout flow with five authenticated API calls can incur 450ms of pure preflight wait time.
Mobile networks amplify the problem. On 4G with 70ms RTT, each preflight costs two round trips (OPTIONS + actual request) versus one. High RTT plus TLS negotiation makes preflights on mobile 2–3x slower than on desktop. This directly degrades Interaction to Next Paint (INP) and Largest Contentful Paint (LCP) when API data blocks rendering.
There is also origin load to consider. OPTIONS requests are lightweight but numerous. At 1,000 requests per second with 30% preflight rate, you are handling 300 unnecessary origin hits per second. Caching at the edge offloads this completely. For high-traffic stores, this is as important as CDN optimization for e-commerce image and API delivery.
Access-Control-Max-Age is missing or set to 5 (the default in many frameworks), browsers will re-issue a preflight for the same URL every 5 seconds. Setting it to 86400 without edge caching still forces the first preflight of each browser session to hit origin.
3. Architecture: Caching Preflights at the Edge
The optimal architecture serves OPTIONS responses directly from CDN edge memory without ever contacting origin, while respecting CORS security semantics. The flow has three layers: browser cache, edge cache, and origin.
Correctness depends on the cache key. A naive cache that keys only on URL will serve the wrong CORS headers to different origins. The edge cache key for OPTIONS must include:
- URL path (including query if your API varies by query)
- Origin header value
- Access-Control-Request-Method
- Access-Control-Request-Headers (sorted, lowercased)
Additionally, the cached response must include Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers so intermediate caches and browsers understand the variance. For credentialed requests, never cache a wildcard Access-Control-Allow-Origin: *. The edge should echo the specific Origin if it is allowlisted, otherwise return 403 without caching.
This design pairs naturally with edge computing — you can run allowlist validation directly in a Worker or Edge Function, eliminating origin entirely for preflights.
4. Implementation: Configuring Your CDN
Most CDNs require explicit configuration to cache OPTIONS. Below are production-tested patterns.
4.1 Origin Headers (Required Baseline)
Your origin must emit cacheable headers on OPTIONS responses. Framework defaults often prevent caching, so override them:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-ID
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Cache-Control: public, max-age=3600, s-maxage=3600
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
Content-Length: 0
Note the dual caching directives: Access-Control-Max-Age controls browser preflight cache, while Cache-Control: s-maxage controls CDN edge cache. Set them to the same value for consistency. Use 204 not 200 to avoid body overhead.
4.2 Edge Logic: Cloudflare Workers / Fastly VCL / AWS CloudFront
if (request.method === "OPTIONS") {
const cacheKey = new Request(url, {
headers: {
"Origin": request.headers.get("Origin"),
"ACRM": request.headers.get("Access-Control-Request-Method"),
"ACRH": request.headers.get("Access-Control-Request-Headers")
}
});
let res = await caches.default.match(cacheKey);
if (res) return res;
res = await fetch(request);
res = new Response(null, res);
res.headers.set("Cache-Control", "public, max-age=3600");
event.waitUntil(caches.default.put(cacheKey, res.clone()));
return res;
}
sub vcl_hash {
if (req.method == "OPTIONS") {
hash_data(req.http.Origin);
hash_data(req.http.Access-Control-Request-Method);
hash_data(req.http.Access-Control-Request-Headers);
}
}
sub vcl_fetch {
if (req.method == "OPTIONS" && beresp.status == 204) {
set beresp.ttl = 1h;
set beresp.http.Vary = "Origin, Access-Control-Request-Method, Access-Control-Request-Headers";
}
}
For CloudFront, create a cache policy that whitelists Origin, Access-Control-Request-Method, and Access-Control-Request-Headers for OPTIONS, and set the TTL to 3600s. Disable cache compression for OPTIONS since there is no body.
5. Tuning Access-Control-Max-Age and Cache TTL
Choosing the right TTL is a tradeoff between performance and policy agility. The table below summarizes practical guidance:
| Strategy | Max-Age / TTL | Preflight Reduction | Best For |
|---|---|---|---|
| No edge cache, browser only (5s default) | 5s | ~10% | Development only |
| Browser cache only, no edge | 600s (10m) | ~55% | Low-traffic APIs |
| Edge + browser (recommended) | 3600s (1h) | ~78% | Most production APIs |
| Long-lived edge + browser | 86400s (24h) | ~92% | Stable public APIs, infrequent CORS changes |
We recommend 3600s as the default. It eliminates the majority of preflights while allowing CORS allowlist updates to propagate within an hour. If you need faster revocation, use edge cache purging by surrogate key (e.g., purge all OPTIONS for /api/*) rather than shortening TTL. Also note browser caps: Chromium limits Access-Control-Max-Age to 7200s (2 hours) regardless of what you send; Firefox allows up to 86400s. Edge cache is not subject to this cap, so even with a 2-hour browser limit, edge caching still saves the origin trip for new visitors.
Avoid setting Access-Control-Max-Age above your edge TTL. If the browser caches longer than the edge, a policy change could leave browsers with stale permissions while the edge has already updated, causing inconsistent failures.
6. Security Considerations and Pitfalls
Caching CORS incorrectly can create security holes or hard-to-debug failures.
- Never cache wildcard with credentials: If
Access-Control-Allow-Credentials: true, the edge must not returnAccess-Control-Allow-Origin: *. Validate Origin against an explicit allowlist at the edge and echo it back. Cache only allowlisted origins. - Vary is mandatory: Omitting
Vary: Origincauses cache poisoning where User A's Origin is served to User B. Some CDNs require you to explicitly enable Vary handling. - Do not cache 403s long: If Origin is not allowed, return 403 with
Cache-Control: no-storeor a very short TTL (e.g., 10s) to avoid caching negative responses that block legitimate future allowlist additions. - Separate cache for OPTIONS vs GET: Ensure your CDN does not serve a cached GET response to an OPTIONS request or vice versa. Method must be part of the cache key or you must use separate cache behaviors.
- Purge on policy change: When you add or remove an allowed origin, purge the edge OPTIONS cache immediately. Stale preflight caches are invisible to users but cause CORS errors that are difficult to reproduce.
When combined with proper DDoS protection at the edge, cached preflights also reduce origin exposure — attackers cannot flood origin with OPTIONS if the edge absorbs them.
7. Measuring Impact
Validate optimization with Real User Monitoring (RUM) and synthetic tests. In Chrome DevTools, filter Network by method:OPTIONS and check the cf-cache-status or x-cache header — you should see HIT for repeat preflights. Compare Time to First Byte before and after: edge-cached OPTIONS should drop from 80–120ms to 8–15ms.
Track two metrics in your analytics: preflight hit ratio (cached OPTIONS / total OPTIONS) and preflight-to-request ratio. A healthy setup achieves >75% hit ratio and reduces preflights per page load by more than half. Pair this with image optimization and preconnect hints for a holistic latency reduction.
Finally, test with credentials and multiple origins. Use curl to verify Vary behavior:
-H "Origin: https://www.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization, Content-Type"
# Expect: 204, Cache-Control: public, max-age=3600, Vary: Origin ...
Repeat with a different Origin and confirm you get a distinct cached response, not a reused one.
Frequently Asked Questions
Set both Access-Control-Max-Age (browser) and edge Cache-Control: s-maxage to 600–3600 seconds. One hour (3600s) is the recommended default — it cuts ~78% of preflights while allowing policy changes to propagate quickly. Browsers cap at 2 hours in Chrome and 24 hours in Firefox, so longer values have diminishing returns.
Yes, when the cache key includes Origin, Access-Control-Request-Method, and Access-Control-Request-Headers, and you use Vary correctly. Only cache responses for allowlisted origins, never cache * with credentials, and avoid long caching of 403 rejections. The cached data is just CORS policy, not user data.
By RFC, OPTIONS is not considered cacheable by default, so CDNs proxy it to origin to avoid accidentally serving stale CORS policies. You must explicitly enable OPTIONS caching, define a CORS-aware cache key, and ensure origin returns Cache-Control: public, max-age=... and Vary headers on OPTIONS responses.
No. Simple requests (GET/POST with safelisted headers) never send a preflight, so there is nothing to cache. Optimization only helps non-simple requests — those with application/json, custom headers like Authorization, or methods like PUT/PATCH/DELETE. Focus your edge rules on API routes that use these.
Optimized preflight handling is one of the highest-ROI CDN tunings for API-heavy applications. With a CORS-aware cache key, a 1-hour TTL, and edge validation of allowed origins, you can serve most preflights in single-digit milliseconds and remove hundreds of milliseconds from critical user flows — all while keeping your CORS policy strict and auditable.