Home / CDN Security / CORS Preflight Optimization
CDN Security

CDN CORS Preflight Optimization: Caching OPTIONS and Reducing Latency

David Park — Performance Engineering Lead Sep 8, 2026 8 min read

The takeaway: Uncached CORS preflights add 80–150ms per API call. By caching OPTIONS responses at the CDN edge and tuning Access-Control-Max-Age, you can eliminate 60–80% of preflight round trips, cut Time to First Byte (TTFB), and improve INP without weakening your CORS policy.

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:

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.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.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.

Key metric: If your API's 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.

CORS Preflight Edge Caching Flow Diagram showing browser, CDN edge, and origin interaction for cached and uncached OPTIONS Browser Cache: Max-Age CDN Edge Cache: OPTIONS Origin API Generates Policy OPTIONS (miss) 1st only HIT: 8–12ms HIT: 0ms (memory) Subsequent preflights served from edge or browser — no origin trip
Figure 1: After the first OPTIONS miss, both browser (via Access-Control-Max-Age) and edge cache serve subsequent preflights. Origin is contacted only on cache miss or policy change.

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:

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:

# Origin response for OPTIONS /api/v1/cart
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

// Cloudflare Worker - cache OPTIONS at edge
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;
}
# Fastly VCL - include CORS headers in cache key
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.

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:

curl -i -X OPTIONS https://api.example.com/v1/cart \
  -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

How long should I cache CORS preflight OPTIONS responses?

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.

Is it safe to cache OPTIONS responses at the CDN edge?

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.

Why is my CDN not caching OPTIONS requests by default?

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.

Do simple CORS requests need preflight caching?

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.

CORS CDN OPTIONS Performance

References & Standards: OWASP CDN Security · Cloudflare documentation