Access-Control-Max-Age: default, 600 vs 86400, measured in Chrome

Access-Control-Max-Age says how many seconds the browser may reuse a CORS preflight (the OPTIONS request) before asking again. We counted the preflights Chrome sent.

1. Preflights for 4 PUTs at 0, 1, 4 and 7 seconds

Access-Control-Max-AgeOPTIONS requestsMeaning
(not sent)2Default 5 seconds: reused at 1 s and 4 s, expired by 7 s
04No caching: a preflight before every request
-14Same as 0
33Reused at 1 s, expired at 4 s and 7 s
6001One preflight for the whole run

2. Which value to send

# nginx: answer preflights directly
if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Max-Age 7200 always;
    add_header Vary Origin always;
    return 204;
}

The $cors_origin allow-list map is in our multiple-origins CORS guide, and why Vary: Origin matters was measured separately. Why a JSON POST/PUT needs a preflight at all: Content-Type application/json.

Frequently asked questions

What is the default Access-Control-Max-Age?

5 seconds in Chrome. With no header, our test saw one preflight reused at 1 s and 4 s and a new one at 7 s.

Is Access-Control-Max-Age 86400 allowed?

Yes, but Chromium caps it at 7200 seconds (2 hours) and Firefox at 86400 (per their documentation).

How do I disable preflight caching?

Send 0 (or -1). In our test both caused a preflight before every request.

Why do I still see OPTIONS requests with a long max-age?

The cache is per URL and origin, so each new path or query string gets its own preflight. Also, DevTools with 'Disable cache' on skips it.

Written with AI assistance. Measured on 2026-10-01 in Chromium 153 (full build): a page on localhost:8994 sent a cross-origin PUT with Content-Type: application/json (which needs a preflight) to 127.0.0.1:8995 at 0, 1, 4 and 7 seconds, and the server counted OPTIONS requests for each Access-Control-Max-Age value. The 2-hour cap for large values is from Chromium's documentation, not measured (it would take 2 hours); so are the Firefox cap, the per-URL cache key, the DevTools note and the nginx block. PlainHeaders is our extension; the other methods are free.