HTTP

Header

Age Header

Learn how the Age header indicates how long a response has been cached in seconds. Understand cache freshness calculations and CDN behavior.

6 min read intermediate Try in Playground

TL;DR: Indicates how long a response has been sitting in a cache (in seconds). Helps determine cache freshness and time until expiration.

What is Age?

The Age header tells you how long (in seconds) a response has been stored in a cache. It’s like a timestamp that says “this content has been sitting in the cache for X seconds.”

When a cache (like a CDN, proxy, or browser cache) serves a stored response, it adds or updates the Age header to indicate the response’s age. This helps clients and intermediaries understand how fresh or stale the cached content is.

How Age Works

First request - cache miss:

GET /api/data HTTP/1.1
Host: api.example.com
```text

**Origin server response:**

```http
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Content-Type: application/json

{"data": "fresh from origin"}

Second request (10 minutes later) - cache hit:

HTTP/1.1 200 OK
Age: 600
Cache-Control: max-age=3600
Content-Type: application/json

{"data": "fresh from origin"}
```text

The Age header shows the response has been cached for 600 seconds (10 minutes).

## Syntax

```http
Age: <delta-seconds>

The value is the number of seconds the response has been in the cache.

# Fresh - just cached
Age: 0

# 5 minutes in cache
Age: 300

# 1 hour in cache
Age: 3600

# 1 day in cache
Age: 86400
```text

## Common Examples

### CDN Cache Hit

```http
HTTP/1.1 200 OK
Age: 1200
Cache-Control: max-age=3600
Content-Type: text/html
X-Cache: HIT

<!DOCTYPE html>...

Content has been in CDN cache for 20 minutes (1200 seconds).

Reverse Proxy Cache

HTTP/1.1 200 OK
Age: 45
Cache-Control: public, max-age=300
Content-Type: application/json
Via: 1.1 varnish

{"data": "value"}
```text

Response has been in Varnish cache for 45 seconds.

### Fresh from Origin

```http
HTTP/1.1 200 OK
Age: 0
Cache-Control: max-age=7200
Content-Type: image/png

[PNG image data]

Response just fetched from origin (Age: 0).

Near Expiration

HTTP/1.1 200 OK
Age: 3500
Cache-Control: max-age=3600
Content-Type: application/json

{"data": "expires soon"}
```text

Response is almost stale (100 seconds until expiration).

## Real-World Scenarios

### CDN with Multiple Cache Layers

**Request through multi-tier cache:**

```http
GET /images/logo.png HTTP/1.1
Host: cdn.example.com

Response from edge cache:

HTTP/1.1 200 OK
Age: 900
Cache-Control: public, max-age=86400
Content-Type: image/png
X-Cache: HIT
X-Cache-Hits: 42

[PNG data]
```javascript

The image has been in the edge cache for 15 minutes, served 42 times.

### API Response Freshness Check

**Client code checking freshness:**

```javascript
fetch('https://api.example.com/data').then((response) => {
  const age = parseInt(response.headers.get('Age') || '0')
  const maxAge = 3600 // from Cache-Control

  const freshnessLifetime = maxAge - age
  const freshnessPercent = (freshnessLifetime / maxAge) * 100

  console.log(`Content is ${freshnessPercent.toFixed(0)}% fresh`)
  console.log(`Expires in ${freshnessLifetime} seconds`)

  return response.json()
})

Stale Content Detection

async function fetchWithFreshnessCheck(url) {
  const response = await fetch(url)
  const age = parseInt(response.headers.get('Age') || '0')
  const cacheControl = response.headers.get('Cache-Control')

  // Parse max-age from Cache-Control
  const maxAgeMatch = cacheControl?.match(/max-age=(\d+)/)
  const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : 0

  const isStale = age >= maxAge

  if (isStale) {
    console.warn('Warning: Serving stale content')
    // Trigger background refresh
    fetch(url, { cache: 'reload' })
  }

  return response.json()
}
```javascript

### Cache Performance Monitoring

```javascript
// Track cache age distribution
const cacheAges = []

fetch('https://api.example.com/data').then((response) => {
  const age = parseInt(response.headers.get('Age') || '0')
  cacheAges.push(age)

  // Calculate average cache age
  const avgAge = cacheAges.reduce((a, b) => a + b, 0) / cacheAges.length
  console.log(`Average cache age: ${avgAge} seconds`)

  return response.json()
})

Age Calculation

Single Cache

Age = time_since_cached

Example:
- Cached at: 10:00:00
- Current time: 10:15:00
- Age: 900 seconds (15 minutes)

Multiple Caches (Accumulative)

Age = age_from_previous_cache + time_in_current_cache

Example:
- Origin → CDN Origin Shield: Age: 300
- CDN Origin Shield → Edge: Age: 300 + 60 = 360
- Client receives: Age: 360

With Validation

# If cache validates with origin and content unchanged
Age resets to 0 or continues from validation time

Freshness Calculation

// Calculate remaining freshness lifetime
function getFreshnessLifetime(response) {
  const age = parseInt(response.headers.get('Age') || '0')
  const cacheControl = response.headers.get('Cache-Control') || ''

  // Extract max-age
  const maxAgeMatch = cacheControl.match(/max-age=(\d+)/)
  const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : 0

  // Extract s-maxage (shared cache max-age)
  const sMaxAgeMatch = cacheControl.match(/s-maxage=(\d+)/)
  const sMaxAge = sMaxAgeMatch ? parseInt(sMaxAgeMatch[1]) : maxAge

  const freshnessLifetime = sMaxAge - age

  return {
    age,
    maxAge: sMaxAge,
    freshnessLifetime,
    isStale: freshnessLifetime <= 0,
    freshnessPercent: Math.max(0, (freshnessLifetime / sMaxAge) * 100)
  }
}

// Usage
fetch('https://api.example.com/data').then((response) => {
  const freshness = getFreshnessLifetime(response)
  console.log(`Age: ${freshness.age}s`)
  console.log(`Freshness: ${freshness.freshnessPercent.toFixed(0)}%`)
  console.log(`Expires in: ${freshness.freshnessLifetime}s`)
  return response.json()
})
```javascript

## Best Practices

### 1. Use Age to Make Informed Decisions

```javascript
// ✅ Check age before using cached data
async function fetchData(url, maxAcceptableAge = 300) {
  const response = await fetch(url)
  const age = parseInt(response.headers.get('Age') || '0')

  if (age > maxAcceptableAge) {
    console.warn(`Data is ${age}s old, forcing refresh`)
    return fetch(url, { cache: 'reload' })
  }

  return response
}

2. Monitor Cache Performance

// ✅ Track cache hit rates and age distribution
class CacheMonitor {
  constructor() {
    this.stats = { hits: 0, misses: 0, ages: [] }
  }

  recordResponse(response) {
    const age = parseInt(response.headers.get('Age') || '0')

    if (age === 0) {
      this.stats.misses++
    } else {
      this.stats.hits++
      this.stats.ages.push(age)
    }
  }

  getStats() {
    const hitRate = this.stats.hits / (this.stats.hits + this.stats.misses)
    const avgAge = this.stats.ages.reduce((a, b) => a + b, 0) / this.stats.ages.length

    return {
      hitRate: (hitRate * 100).toFixed(2) + '%',
      avgAge: avgAge.toFixed(0) + 's'
    }
  }
}
```text

### 3. Set Appropriate Cache-Control

```javascript
// ✅ Server sets proper cache directives
app.get('/api/data', (req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=3600')
  res.json({ data: 'value' })
})

// CDN or proxy will add Age header automatically

4. Handle Stale-While-Revalidate

// ✅ Serve stale content while revalidating
async function fetchWithSWR(url) {
  const response = await fetch(url)
  const age = parseInt(response.headers.get('Age') || '0')
  const cacheControl = response.headers.get('Cache-Control') || ''

  // Check if stale-while-revalidate is allowed
  const swrMatch = cacheControl.match(/stale-while-revalidate=(\d+)/)
  const swrSeconds = swrMatch ? parseInt(swrMatch[1]) : 0

  const maxAgeMatch = cacheControl.match(/max-age=(\d+)/)
  const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : 0

  const isStale = age >= maxAge
  const canServeStale = age < maxAge + swrSeconds

  if (isStale && canServeStale) {
    // Serve stale, revalidate in background
    fetch(url, { cache: 'reload' })
  }

  return response.json()
}
```text

## Cache Age Analysis

### Debugging Cache Behavior

```bash
# Check cache age over time
for i in {1..5}; do
  echo "Request $i:"
  curl -sI https://api.example.com/data | grep -E "Age:|Cache-Control:"
  sleep 60
done

# Output shows increasing Age:
# Request 1: Age: 0
# Request 2: Age: 60
# Request 3: Age: 120
# Request 4: Age: 180
# Request 5: Age: 240

Calculating Time to Expiration

function getTimeToExpiration(response) {
  const age = parseInt(response.headers.get('Age') || '0')
  const cacheControl = response.headers.get('Cache-Control') || ''

  const maxAgeMatch = cacheControl.match(/max-age=(\d+)/)
  const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : 0

  const secondsUntilExpiration = maxAge - age

  if (secondsUntilExpiration <= 0) {
    return 'Expired'
  }

  const minutes = Math.floor(secondsUntilExpiration / 60)
  const seconds = secondsUntilExpiration % 60

  return `${minutes}m ${seconds}s`
}
```text

## Common Patterns

### Progressive Age Headers

```http
# First cache layer (CDN origin)
Age: 100

# Second cache layer (CDN edge)
Age: 150
# (100 from origin + 50 in edge cache)

# Client browser cache
Age: 150
# (browser uses the received Age value)

Age: 0 Scenarios

# Fresh from origin
Age: 0
Cache-Control: max-age=3600

# Just validated and still fresh
Age: 0
Cache-Control: max-age=3600

# No cache (always 0)
Age: 0
Cache-Control: no-cache
```text

## Testing

### Using curl

```bash
# Check age header
curl -sI https://cdn.example.com/image.png | grep Age

# Multiple requests to see age increase
curl -sI https://api.example.com/data | grep -E "Age:|Date:"
sleep 30
curl -sI https://api.example.com/data | grep -E "Age:|Date:"

# Force cache bypass
curl -sI https://api.example.com/data -H "Cache-Control: no-cache"

Using JavaScript

// Monitor age over multiple requests
async function monitorCacheAge(url, requests = 5, interval = 5000) {
  for (let i = 0; i < requests; i++) {
    const response = await fetch(url)
    const age = response.headers.get('Age')
    console.log(`Request ${i + 1}: Age = ${age || '0'}s`)

    if (i < requests - 1) {
      await new Promise((resolve) => setTimeout(resolve, interval))
    }
  }
}

monitorCacheAge('https://api.example.com/data')
  • Cache-Control - Caching directives and max-age
  • Expires - Absolute expiration time
  • ETag - Resource version for validation
  • Last-Modified - Last modification time
  • Vary - Cache key variations
  • Via - Proxy and cache chain information

Frequently Asked Questions

What is the Age header?

The Age header indicates how long a response has been in a proxy cache, measured in seconds. It helps clients understand how fresh cached content is.

How is Age calculated?

Age equals the time since the response was generated by the origin server. Each cache adds its residence time. A response with Age: 3600 has been cached for 1 hour.

What does Age: 0 mean?

Age: 0 means the response was just fetched from the origin server or the cache just received it. The content is as fresh as possible from the cache.

How does Age relate to max-age?

If Age exceeds max-age, the cached response is stale. For example, with max-age=3600 and Age: 4000, the response is 400 seconds past its freshness lifetime.

Keep Learning