# 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.

Source: https://howhttpworks.com/headers/access-control-max-age
Last reviewed: 2026-10-04

> **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:**

```http
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
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:**

```http
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

```http
Access-Control-Max-Age: <delta-seconds>
```

The value is the number of seconds the preflight response can be cached.

```http
# 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
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
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
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
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:**

```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

```javascript
// 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

```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

```javascript
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.

```javascript
// 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)

```text
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)

```text
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)

```text
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

```javascript
// ❌ 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

```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

```javascript
// 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

```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

```bash
# 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

```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

```bash
# 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

```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

```javascript
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](https://howhttpworks.com/headers/access-control-allow-origin) - Allowed origins
- [Access-Control-Allow-Methods](https://howhttpworks.com/headers/access-control-allow-methods) - Allowed HTTP methods
- [Access-Control-Allow-Headers](https://howhttpworks.com/headers/access-control-allow-headers) - Allowed request headers
- [Access-Control-Allow-Credentials](https://howhttpworks.com/headers/access-control-allow-credentials) - Credentials support
- [Access-Control-Request-Method](https://howhttpworks.com/headers/access-control-request-method) - Preflight method request
- [Access-Control-Request-Headers](https://howhttpworks.com/headers/access-control-request-headers) - Preflight headers request
