The most surprising thing about CDN Time-To-Live (TTL) is that setting it too low is almost always worse for both your users and your infrastructure than setting it too high.

Let’s see this in action. Imagine a simple static asset, like a logo image.

<img src="https://your-cdn.com/images/logo.png" alt="Company Logo">

When a user’s browser requests this logo.png, the request first hits your CDN. The CDN checks its cache. If it has a fresh copy (within its TTL), it serves it directly. If not, it fetches the asset from your origin server, caches it, and then serves it to the user. Subsequent requests for the same asset within the TTL window will be served from the CDN’s cache.

The goal with CDN TTLs is to strike a delicate balance. On one hand, you want content to be fresh, meaning users see the latest versions of your assets. On the other hand, you want to maximize cache efficiency, meaning the CDN serves as many requests as possible from its edge servers, reducing latency and load on your origin.

Here’s the mental model:

  • Cache Hit: A request for an asset that the CDN already has in its cache and is still valid (within its TTL). This is fast and offloads your origin.
  • Cache Miss: A request for an asset that the CDN does not have, or whose cached copy has expired. The CDN must fetch it from your origin server. This is slower and hits your origin.
  • Origin Server: Your actual web server where the original assets are stored.

The Cache-Control HTTP header, specifically the max-age directive, is the primary mechanism for controlling TTL. For example, Cache-Control: public, max-age=31536000 tells the CDN (and the browser) that this asset can be cached for one year (31,536,000 seconds).

You control TTL at the CDN configuration level, often through rules that match specific URL patterns or file types. For instance, you might set a long TTL for static assets like images, CSS, and JavaScript, and a very short TTL for dynamic content or HTML pages.

Consider these common scenarios and their TTL strategies:

  • Immutable Assets (e.g., logo.png, app-v1.2.3.js): These files never change once deployed. You can set a very long TTL, like max-age=31536000 (1 year). When you deploy a new version, you change the filename (e.g., app-v1.2.4.js) so that browsers and CDNs request the new file, effectively bypassing the old cache. This is the most efficient setup.
  • Versioned Assets (e.g., main.a1b2c3d4.css): Similar to immutable assets, these have a hash in their filename that changes only when the content changes. A long TTL like max-age=31536000 is ideal here.
  • Infrequently Changing Assets (e.g., robots.txt, sitemap.xml): These might change, but not often. A TTL of max-age=86400 (1 day) or max-age=3600 (1 hour) is reasonable.
  • Frequently Changing Content (e.g., HTML pages, API responses): These require shorter TTLs. max-age=60 (1 minute) or even max-age=0 (effectively no cache, or no-cache which means revalidation) might be necessary. However, max-age=0 forces a revalidation for every request, which can still be inefficient. A better approach for dynamic content might be Cache-Control: public, max-age=60, stale-while-revalidate=60. This allows the CDN to serve a stale version for up to 60 seconds while it asynchronously revalidates with the origin.

The stale-while-revalidate directive is a powerful tool. When a cache entry is stale but stale-while-revalidate is set, the CDN can serve the stale content immediately while simultaneously making a background request to your origin to fetch a fresh copy. Once the fresh copy is received, it’s stored for the next request. This significantly improves perceived performance for users without them necessarily seeing outdated content, and it reduces the load on your origin compared to a strict max-age=0 or no-cache.

When you set your TTLs too low for static assets, say max-age=60 for an image that rarely changes, you force the CDN to check with your origin every minute for that asset. This increases requests to your origin server, potentially leading to higher infrastructure costs and slower response times for your users if your origin becomes overloaded. It also means a higher chance of cache misses, defeating a primary purpose of a CDN. You end up paying for a CDN’s global network while still heavily relying on your origin.

The next concept you’ll grapple with is cache invalidation strategies when TTLs are not granular enough, or when you need to force an update before TTL expires.

Want structured learning?

Take the full Cdn course →