Loading learning content...
Every cached object in a CDN has an invisible timer counting down. When that timer reaches zero, the cached object is considered stale—potentially outdated and requiring re-validation or replacement from the origin server. This timer is defined by the Time-To-Live (TTL).
TTL configuration sits at the heart of the fundamental CDN trade-off: performance versus freshness. A TTL that's too short means excessive origin traffic, higher latency for users, and reduced cache efficiency. A TTL that's too long means users see outdated content, potentially causing confusion, broken functionality, or even security risks.
Getting TTL right isn't about finding a single magic number—it's about understanding the characteristics of your content and building a systematic strategy that optimizes for your specific use cases.
By the end of this page, you will understand how TTL values are determined from multiple sources, strategies for configuring TTLs across different content types, advanced patterns like heuristic caching and TTL layering, and how to avoid the common pitfalls that degrade cache efficiency.
TTL (Time-To-Live) specifies how long a cached object remains fresh—usable without checking with the origin server. Once the TTL expires, the cached object becomes stale and must either be re-validated (checking if it's still current) or replaced (fetching a new copy).
The TTL Lifecycle:
Multiple TTL values may apply to a single cached object, and CDNs must reconcile these to determine actual behavior.
123456789101112131415161718192021222324
# Source 1: Cache-Control max-age (highest priority)HTTP/1.1 200 OKCache-Control: public, max-age=86400# TTL = 86400 seconds = 24 hours from response time # Source 2: Cache-Control s-maxage (overrides max-age for shared caches)HTTP/1.1 200 OKCache-Control: public, max-age=3600, s-maxage=86400# Browser TTL = 3600s, CDN TTL = 86400s # Source 3: Expires header (legacy, lower priority than Cache-Control)HTTP/1.1 200 OKExpires: Thu, 15 Jan 2026 12:00:00 GMT# TTL = difference between Expires and response Date # Source 4: CDN configuration (can override origin headers)# Cloudflare: Browser TTL = 4 hours, Edge TTL = 2 hours# AWS CloudFront: Minimum TTL = 0, Maximum TTL = 31536000, Default TTL = 86400 # Source 5: Heuristic caching (when no explicit TTL)HTTP/1.1 200 OKLast-Modified: Mon, 01 Jan 2024 00:00:00 GMTDate: Mon, 01 Jan 2025 00:00:00 GMT# Heuristic TTL = 10% of (Date - Last-Modified) = ~36.5 daysWhen multiple TTL indicators exist, the priority is generally: Cache-Control: s-maxage > Cache-Control: max-age > Expires header > CDN default configuration > Heuristic caching. However, CDN configurations can override origin headers, so the actual priority depends on your CDN's settings.
Understanding exactly how CDNs determine whether a cached object is fresh requires knowing how Age is tracked and how it interacts with TTL values.
1234567891011121314151617181920212223242526272829303132
FRESHNESS MODEL: fresh = (current_age < freshness_lifetime) Where: current_age = time since object was cached (seconds) freshness_lifetime = TTL value from headers EXAMPLE: Response received at: 10:00:00 UTCCache-Control: max-age=3600 (1 hour)freshness_lifetime = 3600 seconds At 10:30:00 UTC: current_age = 1800 seconds fresh = (1800 < 3600) = TRUE → serve from cache At 11:30:00 UTC: current_age = 5400 seconds fresh = (5400 < 3600) = FALSE → object is stale AGE HEADER: When CDN serves a cached response, it includes an Age header: HTTP/1.1 200 OKAge: 1800Cache-Control: max-age=3600 This tells clients: "This response has been cached for 1800 seconds"Clients calculate remaining freshness: 3600 - 1800 = 1800 seconds remainingWhy Age Matters for Multi-Layer Caching:
In CDN architectures with multiple cache layers (edge → regional → shield → origin), the Age header accumulates across layers:
Cache-Control: max-age=3600Age: 600Age: 900Age: 1100This is why CDN TTL settings at different layers matter. If your edge cache has a lower TTL than the object's remaining Age, it might re-validate or refetch more frequently than necessary.
If your origin sets a TTL of 1 hour but your object takes 20 minutes to propagate through cache layers (accumulating Age), clients only see 40 minutes of freshness. For short TTLs, this can cause unexpectedly frequent re-validation. Consider this when setting origin TTLs for multi-tier CDN architectures.
Different content types have fundamentally different caching characteristics. Elite CDN configurations recognize these differences and apply appropriate TTL strategies to each.
| Content Type | Typical TTL | Rationale | Invalidation Strategy |
|---|---|---|---|
| Static assets with hash (app.abc123.js) | 1 year (31536000s) | Content hash guarantees uniqueness; never changes | Not needed—new hash = new URL |
| Versioned assets (/v2/styles.css) | 1 month | Version in path provides cache-busting | Update version on change |
| Unversioned assets (/styles.css) | 1 hour to 1 day | Balance freshness with efficiency | URL versioning or purge API |
| API responses (read-heavy) | 1 minute to 1 hour | Depends on acceptable staleness window | Purge on data mutation |
| API responses (read/write) | 0 or no-store | Mutations require immediate consistency | Don't cache—or use private caching |
| User-generated content | 1 hour | Updates are infrequent but should propagate | Purge on edit |
| News/blog content | 5 minutes to 1 hour | Must reflect updates reasonably quickly | Purge on publish/update |
| Marketing pages | 1 day to 1 week | Changes are planned and can use purges | Scheduled purge after deploy |
| Images (uploaded) | 1 month to 1 year | Rarely change; high bandwidth cost | URL versioning or purge |
| Video/media | 1 month to 1 year | High bandwidth; changes are rare | URL versioning strongly preferred |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
E-COMMERCE CDN TTL CONFIGURATION STATIC ASSETS (JavaScript, CSS with hashed filenames)────────────────────────────────────────────────────Path Pattern: /_next/static/**/* /assets/**/*.*.js /assets/**/*.*.cssTTL: 1 year (immutable)Cache-Control: public, max-age=31536000, immutableRationale: Content-addressed filenames ensure unique URLs for every change PRODUCT IMAGES────────────────────────────────────────────────────Path Pattern: /images/products/**/*TTL: 30 daysCache-Control: public, max-age=2592000Invalidation: Purge by URL on image replacementRationale: Images rarely change; high bandwidth savings PRODUCT PAGES (HTML)────────────────────────────────────────────────────Path Pattern: /products/*TTL: 5 minutes (edge), stale-while-revalidate for 1 hourCache-Control: public, max-age=300, stale-while-revalidate=3600Rationale: Price/inventory can change; 5-minute staleness is acceptable PRODUCT API (JSON)────────────────────────────────────────────────────Path Pattern: /api/products/*TTL: 1 minuteCache-Control: public, max-age=60, s-maxage=60Rationale: APIs used for real-time updates need fresher data CART/CHECKOUT────────────────────────────────────────────────────Path Pattern: /cart/*, /checkout/*TTL: 0 (no caching)Cache-Control: private, no-storeRationale: User-specific, real-time state; must never be cached publicly HOMEPAGE────────────────────────────────────────────────────Path Pattern: /TTL: 1 minute (edge), stale-while-revalidate for 5 minutesCache-Control: public, max-age=60, stale-while-revalidate=300Rationale: Personalized elements handled client-side; base page can cacheFor content-addressed assets (files with content hashes in their filenames), use Cache-Control: immutable. This tells browsers and CDNs that the content will never change for this URL, eliminating unnecessary revalidation requests. Modern build tools like Webpack, Vite, and Next.js generate these hashed filenames automatically.
CDNs provide configuration options to override origin TTL values. Understanding when and how to use these overrides is essential for sophisticated cache management.
max-age=0, CDN caches for at least this duration. Use for content where origin misconfigures or doesn't send cache headers.max-age=31536000, CDN limits caching to this duration. Use to ensure regular origin checks for frequently updated content.123456789101112131415161718192021222324252627282930313233343536373839404142434445
# AWS CloudFront Cache Policy (CloudFormation)CachePolicy: Type: AWS::CloudFront::CachePolicy Properties: CachePolicyConfig: Name: OptimizedStaticAssets Comment: Long caching for static assets with origin override # TTL Configuration MinTTL: 86400 # Minimum 1 day (even if origin says less) MaxTTL: 31536000 # Maximum 1 year (cap long TTLs) DefaultTTL: 2592000 # Default 30 days (if no origin headers) # Cache Key Parameters ParametersInCacheKeyAndForwardedToOrigin: # Accept-Encoding for compression variants EnableAcceptEncodingBrotli: true EnableAcceptEncodingGzip: true # Headers to include in cache key HeadersConfig: HeaderBehavior: whitelist Headers: - Accept-Language # For localized content # Query strings to include QueryStringsConfig: QueryStringBehavior: whitelist QueryStrings: - version - format # Cookies (usually none for static assets) CookiesConfig: CookieBehavior: none # Separate policy for dynamic contentDynamicCachePolicy: Type: AWS::CloudFront::CachePolicy Properties: CachePolicyConfig: Name: DynamicContentShortTTL MinTTL: 0 # Respect origin no-cache MaxTTL: 300 # Maximum 5 minutes DefaultTTL: 60 # Default 1 minuteTTL overrides that ignore origin headers can cause serious problems. If you force a 24-hour TTL on content the origin expects to be fresh, users may see stale data for a full day. Always understand why origin sends specific headers before overriding them. Overrides are useful for fixing broken or missing origin headers, not for bypassing intentional origin decisions.
A sophisticated caching strategy often requires different TTL values for browser caches versus CDN caches. This enables aggressive CDN caching while maintaining control over client-visible staleness.
s-maxage directivemax-age directive123456789101112131415161718192021222324252627282930313233
# Strategy: Long CDN TTL, Short Browser TTL# Enables aggressive CDN caching while limiting browser staleness HTTP/1.1 200 OKContent-Type: application/jsonCache-Control: public, max-age=60, s-maxage=86400, stale-while-revalidate=60 # BEHAVIOR:# # Browser:# - Caches response for 60 seconds# - After 60s, makes conditional request (If-None-Match)# - User sees fresh content within 1 minute of changes## CDN:# - Caches response for 86400 seconds (24 hours)# - Can be purged instantly when content changes# - Reduces origin load by 99.9% during normal operation## On Content Update:# 1. Content changes in database# 2. Application triggers CDN purge# 3. CDN immediately fetches fresh content# 4. Browsers get fresh content on next request (within 60s) # Alternative: CDN-Headers extension (Cloudflare)HTTP/1.1 200 OKContent-Type: application/jsonCache-Control: public, max-age=60CDN-Cache-Control: max-age=86400 # CDN respects CDN-Cache-Control for edge caching# Browsers only see Cache-Control: max-age=60Why Split TTLs Matter:
Control over invalidation — You can purge CDN caches instantly when content changes, but you cannot clear browser caches. Short browser TTLs ensure users see updates quickly.
Cost optimization — Long CDN TTLs minimize origin requests and bandwidth costs. Short browser TTLs are free—they just reduce client-side cache duration.
Consistency — With long browser TTLs, different users may see different content versions for extended periods. Short browser TTLs ensure more consistent experience across users after updates.
Recovery from mistakes — If you deploy broken content, CDN purges fix it globally. But browsers with long TTLs will continue serving broken content until TTL expires.
For content that can be purged but needs reasonable freshness, consider max-age=60, s-maxage=86400. The 1-minute browser TTL is long enough to benefit from browser caching during a session but short enough that updates propagate quickly. Combined with aggressive CDN caching, this achieves 99%+ cache hit ratios while maintaining a 1-minute maximum staleness window.
Different HTTP status codes warrant different caching behaviors. A 200 OK should be cached differently than a 404 Not Found, and error responses require special consideration.
| Status Code | Recommended TTL | Rationale | Notes |
|---|---|---|---|
| 200 OK | Content-dependent | Normal caching behavior | Apply content-type strategies |
| 301 Moved Permanently | Long (1 day to 1 year) | Redirect is permanent by definition | Be sure before caching long |
| 302/307 Temporary Redirect | Short (1 minute) or no-cache | Redirect may change | Consider not caching |
| 304 Not Modified | N/A (uses existing TTL) | Validates existing cached content | Resets freshness timer |
| 404 Not Found | Short (5-60 seconds) | Resource might be created | Prevents 404 storm to origin |
| 410 Gone | Long (1 day to 1 year) | Resource permanently removed | Safe to cache long |
| 500 Internal Server Error | 0 or very short | Transient error, should retry | Never cache server errors |
| 502 Bad Gateway | 0 or very short | Upstream issue, may resolve | Cache briefly to prevent storm |
| 503 Service Unavailable | Short (10-60 seconds) | Temporary unavailability | Respects Retry-After if present |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
// Cloudflare Worker: Apply TTL based on response statusaddEventListener('fetch', event => { event.respondWith(handleRequest(event));}); async function handleRequest(event) { const request = event.request; // Attempt to fetch from origin const response = await fetch(request); // Clone response to modify headers const modifiedResponse = new Response(response.body, response); // Apply status-based caching const ttlConfig = getTTLForStatus(response.status); if (ttlConfig.cache) { modifiedResponse.headers.set( 'Cache-Control', `public, max-age=${ttlConfig.browserTTL}, s-maxage=${ttlConfig.edgeTTL}` ); // Set Cloudflare-specific cache TTL modifiedResponse.headers.set( 'CDN-Cache-Control', `max-age=${ttlConfig.edgeTTL}` ); } else { modifiedResponse.headers.set('Cache-Control', 'no-store'); } return modifiedResponse;} function getTTLForStatus(status) { const configs = { // Success responses - content-dependent, using reasonable defaults 200: { cache: true, browserTTL: 60, edgeTTL: 3600 }, 201: { cache: false }, // Created - usually user-specific // Redirects 301: { cache: true, browserTTL: 86400, edgeTTL: 86400 }, // Permanent 302: { cache: true, browserTTL: 60, edgeTTL: 60 }, // Temporary 307: { cache: true, browserTTL: 60, edgeTTL: 60 }, // Temporary 308: { cache: true, browserTTL: 86400, edgeTTL: 86400 }, // Permanent // Not found - cache briefly to prevent origin storm 404: { cache: true, browserTTL: 30, edgeTTL: 60 }, 410: { cache: true, browserTTL: 86400, edgeTTL: 86400 }, // Gone // Client errors - don't cache (might be fixed by different request) 400: { cache: false }, 401: { cache: false }, 403: { cache: false }, // Server errors - never cache 500: { cache: false }, 502: { cache: true, browserTTL: 0, edgeTTL: 10 }, // Brief to prevent cascade 503: { cache: true, browserTTL: 0, edgeTTL: 30 }, // Brief with retry 504: { cache: false }, }; return configs[status] || { cache: false };}Caching 404s is important to prevent origin storms when missing resources are requested repeatedly. But cache them too long and newly created resources remain invisible. A common mistake: someone requests /user/newuser before the user is created, CDN caches the 404 for 24 hours, and the new user's profile is invisible for a day. Keep 404 TTLs short (30-60 seconds).
When an origin response includes no explicit cache lifetime (max-age, s-maxage, or Expires), CDNs and browsers may apply heuristic caching—inferring a TTL based on other response characteristics.
123456789101112131415161718192021222324252627
HEURISTIC CACHING FORMULA (per HTTP spec): heuristic_ttl = (date_value - last_modified_value) × 0.10 EXAMPLE: Response Headers: Date: Mon, 01 Jan 2025 00:00:00 GMT Last-Modified: Mon, 01 Jul 2024 00:00:00 GMT (No Cache-Control, no Expires) Calculation: Time since modification: 184 days (Jul 1 to Jan 1) Heuristic TTL: 184 × 0.10 = 18.4 days RESULT: Cache may store this response for ~18 days KEY INSIGHT:Files that haven't changed for a long time are assumed to be stableand can be cached longer. This is a reasonable heuristic for staticcontent but dangerous for dynamic content with stale Last-Modified. PROBLEMS WITH HEURISTIC CACHING:1. Different implementations use different multipliers (5% to 50%)2. Old Last-Modified on frequently changing content → excessive caching3. Missing Last-Modified may default to 0% or not cache at all4. Behavior varies significantly between CDN providersWhy You Should Set Explicit TTLs:
Heuristic caching seems convenient—the cache just figures out a reasonable TTL. But it creates several problems:
Unpredictable behavior — Different caches calculate different TTLs. User experience varies by browser, CDN, and proxy.
False assumptions — A file last modified 2 years ago might still change frequently (e.g., a dynamically generated response with a stale Last-Modified).
Debugging difficulty — When caching behaves unexpectedly, implicit heuristics are hard to trace. Explicit TTLs are visible and auditable.
Missed optimization — Heuristic TTLs are conservative. Your CSS file that genuinely never changes gets cached for 10% of its age instead of 1 year.
Best Practice: Always set explicit Cache-Control headers. Use CDN default TTL configuration as a safety net for legacy origins, not as a primary strategy.
Configure your CDN's default TTL to a reasonable value (e.g., 1 hour) for requests without explicit cache headers. This prevents heuristic caching from producing surprising results while providing basic caching for legacy content. But always aim to add proper Cache-Control headers to your origin.
TTL configuration is the temporal dimension of caching strategy. Getting it right determines how efficiently your CDN operates and how fresh your users' experience remains.
s-maxage > max-age > Expires > CDN config > heuristic. Know the priority order.s-maxage for long CDN caching with short max-age for fast browser updates.Cache-Control headers for predictable, optimal behavior.What's Next:
We've covered what makes cached content expire. But modern HTTP caching isn't just about TTL—it's about the sophisticated Cache-Control headers that give you fine-grained control over caching behavior. The next page dives deep into Cache-Control directives, from the basics to advanced features like no-transform and must-understand.
You now understand TTL configuration at a production level—from basic expiration mechanics to sophisticated multi-layer caching strategies. This knowledge enables you to configure CDN caching that maximizes efficiency while maintaining content freshness appropriate for each resource type.