- Home
- HTTP Headers
- Access-Control-Max-Age Header
Header
Access-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.
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
```http
**Server Response - Cache for 1 hour:**
```http
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"}
```text
For the next hour, the browser skips the preflight and directly sends the POST request.
## Syntax
```http
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)
```http
## Common Examples
### Short Cache Duration (5 minutes)
```http
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
```http
Balanced approach for most applications.
### Long Cache Duration (24 hours)
```http
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
```text
Useful during development when CORS configuration might change frequently.
## Real-World Scenarios
### Production API - Optimal Performance
**Server configuration:**
```javascript
// 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)
})
```text
### High-Traffic API - Aggressive Caching
```javascript
// 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)
}
})
```text
## Browser Limitations
Different browsers have different maximum cache times:
- **Firefox**: Maximum 24 hours (86400 seconds)
- **Chrome/Chromium**: Maximum 2 hours (7200 seconds) - older versions; newer versions: 2 hours default, but can go up to 24 hours
- **Safari**: Maximum 24 hours (86400 seconds)
```javascript
// Safe cross-browser value
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)
Best Practices
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
```javascript
### 2. Environment-Specific Configuration
```javascript
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)
})
```text
### 4. Complete Preflight Response
```javascript
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
```text
### Verify Cache Duration
```javascript
// 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
```javascript
## Common Patterns
### Progressive Enhancement
```javascript
// 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 this: Chrome at 2 hours, Firefox at 24 hours. 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.