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-Age | OPTIONS requests | Meaning |
|---|---|---|
| (not sent) | 2 | Default 5 seconds: reused at 1 s and 4 s, expired by 7 s |
0 | 4 | No caching: a preflight before every request |
-1 | 4 | Same as 0 |
3 | 3 | Reused at 1 s, expired at 4 s and 7 s |
600 | 1 | One preflight for the whole run |
2. Which value to send
- Without the header, a busy single-page app makes a preflight round trip every 5 seconds per URL. That's the main thing this header fixes.
600(10 minutes) to7200(2 hours) is a sensible range. Chromium caps the value at 7200 and Firefox at 86400, so86400gives Chrome users 2 hours, not a day.- The cache is per page origin and exact request URL, so different paths or query strings each get their own preflight.
- Keep it short while you are still changing the allowed methods or headers: browsers keep using the old preflight answer until it expires.
# 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.