Response header
< Access-Control-Max-Age: 600Access-Control-Max-Age Header
Learn how Access-Control-Max-Age specifies how long browsers can cache CORS preflight results. Reduce preflight requests and improve cross-origin performance.
- Direction
- Response
- Category
- CORS
- Spec
- Fetch Standard
On this page
- What is Access-Control-Max-Age?
- How Access-Control-Max-Age Works
- Syntax
- Common Examples
- Short Cache Duration (5 minutes)
- Medium Cache Duration (1 hour)
- Long Cache Duration (24 hours)
- No Caching (Development)
- Real-World Scenarios
- Production API - Optimal Performance
- Development Environment - No Caching
- High-Traffic API - Aggressive Caching
- Dynamic CORS with Moderate Caching
- Browser Limitations
- Performance Impact
- Without Caching (Max-Age: 0)
- With Caching (Max-Age: 3600)
- High Frequency (100 requests/hour)
- Getting Access-Control-Max-Age right
- 1. Choose Appropriate Cache Duration
- 2. Environment-Specific Configuration
- 3. Consider Your Update Frequency
- 4. Complete Preflight Response
- Testing
- Check Preflight Caching
- Verify Cache Duration
- Test Different Values
- Common Patterns
- Progressive Enhancement
- Per-Endpoint Configuration
- Related Headers
TL;DR: Specifies how long browsers can cache CORS preflight results (in seconds). Reduces preflight requests for better performance.
What is Access-Control-Max-Age?
The Access-Control-Max-Age header tells browsers how long (in seconds) they can cache the results of a CORS preflight request. This improves performance by reducing the number of preflight OPTIONS requests needed for cross-origin requests.
Think of it like a “remember this permission for X seconds” instruction. Instead of asking for permission every time, the browser can remember the answer and skip the preflight check for subsequent requests.
How Access-Control-Max-Age Works
First Request - Preflight:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
Server Response - Cache for 1 hour:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Subsequent Requests - Skip Preflight:
POST /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
{"name": "John"}
For the next hour, the browser skips the preflight and directly sends the POST request.
Syntax
Access-Control-Max-Age: <delta-seconds>
The value is the number of seconds the preflight response can be cached.
# Cache for 5 minutes
Access-Control-Max-Age: 300
# Cache for 1 hour
Access-Control-Max-Age: 3600
# Cache for 24 hours
Access-Control-Max-Age: 86400
# Don't cache (always preflight)
Access-Control-Max-Age: 0
# Don't cache (header omitted)
# (no Access-Control-Max-Age header)
Common Examples
Short Cache Duration (5 minutes)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 300
Good for APIs where permissions might change frequently.
Medium Cache Duration (1 hour)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Balanced approach for most applications.
Long Cache Duration (24 hours)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
Access-Control-Max-Age: 86400
Good for stable APIs with consistent CORS policies.
No Caching (Development)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 0
Useful during development when CORS configuration might change frequently.
Real-World Scenarios
Production API - Optimal Performance
Server configuration:
// Node.js/Express
app.options('/api/*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '7200') // 2 hours
res.sendStatus(204)
})
Impact:
- First request: Preflight + actual request = 2 round trips
- Next 2 hours: No preflight, just actual request = 1 round trip
- Performance improvement: 50% reduction in requests
Development Environment - No Caching
// Development configuration
const isDevelopment = process.env.NODE_ENV === 'development'
app.options('/api/*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', isDevelopment ? '0' : '3600')
res.sendStatus(204)
})
High-Traffic API - Aggressive Caching
// High-traffic public API
app.options('/api/public/*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
res.setHeader('Access-Control-Max-Age', '86400') // 24 hours
res.sendStatus(204)
})
Dynamic CORS with Moderate Caching
app.options('/api/*', (req, res) => {
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
'https://mobile.example.com'
]
const origin = req.headers.origin
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Max-Age', '1800') // 30 minutes
res.sendStatus(204)
} else {
res.sendStatus(403)
}
})
Browser Limitations
Different browsers have different maximum cache times:
- Firefox: capped at 24 hours (86400 seconds).
- Chrome/Chromium: capped at 2 hours (7200 seconds) since Chrome 76; 10 minutes (600 seconds) before that.
- Safari (WebKit): capped at 10 minutes (600 seconds).
- Default when the header is absent: 5 seconds, per the Fetch Standard.
// Effective in Chromium and Firefox; Safari caps at 600 regardless
res.setHeader('Access-Control-Max-Age', '7200') // 2 hours
Performance Impact
Without Caching (Max-Age: 0)
Request 1: OPTIONS + POST = 2 requests
Request 2: OPTIONS + POST = 2 requests
Request 3: OPTIONS + POST = 2 requests
Total: 6 requests
With Caching (Max-Age: 3600)
Request 1 (0:00): OPTIONS + POST = 2 requests
Request 2 (0:05): POST only = 1 request (cached)
Request 3 (0:10): POST only = 1 request (cached)
Request 4 (1:05): OPTIONS + POST = 2 requests (cache expired)
Total: 5 requests (16% reduction)
High Frequency (100 requests/hour)
Without caching: 200 requests
With Max-Age: 3600: 102 requests (49% reduction)
With Max-Age: 7200: 101 requests (49.5% reduction)
Getting Access-Control-Max-Age right
1. Choose Appropriate Cache Duration
// ❌ Too short - unnecessary preflights
res.setHeader('Access-Control-Max-Age', '30') // 30 seconds
// ❌ Too long - can't quickly update CORS policy
res.setHeader('Access-Control-Max-Age', '604800') // 7 days
// ✅ Balanced - good performance, reasonable update window
res.setHeader('Access-Control-Max-Age', '3600') // 1 hour
2. Environment-Specific Configuration
const maxAge = {
development: 0, // No cache - easy debugging
staging: 1800, // 30 minutes - moderate cache
production: 7200 // 2 hours - optimal performance
}
res.setHeader('Access-Control-Max-Age', maxAge[process.env.NODE_ENV])
3. Consider Your Update Frequency
// Stable API - long cache
app.options('/api/v1/*', (req, res) => {
res.setHeader('Access-Control-Max-Age', '86400') // 24 hours
// ... other headers
res.sendStatus(204)
})
// Experimental API - short cache
app.options('/api/beta/*', (req, res) => {
res.setHeader('Access-Control-Max-Age', '300') // 5 minutes
// ... other headers
res.sendStatus(204)
})
4. Complete Preflight Response
app.options('/api/*', (req, res) => {
// All necessary CORS headers together
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')
res.setHeader('Access-Control-Max-Age', '3600')
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.sendStatus(204)
})
Testing
Check Preflight Caching
# First request - should include preflight
curl -X OPTIONS https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
# Look for:
# Access-Control-Max-Age: 3600
Verify Cache Duration
// Browser console
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Test' })
})
// Check Network tab:
// First request: OPTIONS + POST
// Subsequent requests: POST only (if within cache period)
Test Different Values
# Test short cache
curl -X OPTIONS https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-v | grep "Access-Control-Max-Age"
# Expected: Access-Control-Max-Age: 3600
Common Patterns
Progressive Enhancement
// Start conservative, increase as stable
const appAge = Date.now() - appStartTime
const maxAge = appAge < 3600000 ? 300 : 3600 // 5 min → 1 hour
res.setHeader('Access-Control-Max-Age', String(maxAge))
Per-Endpoint Configuration
const corsConfig = {
'/api/public': { maxAge: 86400 }, // 24 hours
'/api/auth': { maxAge: 1800 }, // 30 minutes
'/api/admin': { maxAge: 300 } // 5 minutes
}
app.options('/api/*', (req, res) => {
const config = corsConfig[req.path] || { maxAge: 3600 }
res.setHeader('Access-Control-Max-Age', String(config.maxAge))
// ... other headers
res.sendStatus(204)
})
Related Headers
- Access-Control-Allow-Origin - Allowed origins
- Access-Control-Allow-Methods - Allowed HTTP methods
- Access-Control-Allow-Headers - Allowed request headers
- Access-Control-Allow-Credentials - Credentials support
- Access-Control-Request-Method - Preflight method request
- Access-Control-Request-Headers - Preflight headers request
Frequently asked questions
What is Access-Control-Max-Age?
This header specifies how long browsers can cache preflight results in seconds. It reduces preflight requests by reusing cached CORS permissions.
What is a good max-age value?
Common values are 86400 (1 day) or 7200 (2 hours). Browsers cap it: Chromium at 2 hours (7200), Firefox at 24 hours (86400), Safari at 10 minutes (600). Balance between performance and flexibility to change CORS policy.
Does max-age affect all CORS requests?
No, only preflight (OPTIONS) results are cached. Simple requests that do not trigger preflight are not affected by this header.
How do I clear the preflight cache?
Users can clear browser cache. Developers can use different URLs or wait for expiration. During development, use short max-age or disable cache in DevTools.
Sources
Related
Preflight Request (CORS OPTIONS)
A CORS preflight is an automatic OPTIONS request the browser sends before a cross-origin request to check permission. See when it fires and how to answer it.
Timing-Allow-Origin
Learn how the Timing-Allow-Origin header specifies which origins can access Resource Timing API data. Enable cross-origin performance monitoring securely.
Content-Encoding
Learn how Content-Encoding specifies compression algorithms (gzip, br, deflate) used to encode response bodies. Reduce bandwidth and improve load times.
Early-Hints
Learn how 103 Early Hints allows servers to send preload hints while preparing the main response. Improve page load performance with early resource loading.