We believe latency and caching are crucial subjects we need to talk about. Loading times are a critical component in the user's experience for all apps and websites: new pages and screens should load in less than a second for end-users to have a smooth experience.
At Koyeb, we provide a global edge network with a built-in CDN to help you
achieve that with caching and TLS termination at the edge, but there is an important topic which is always your
responsibility when you use a CDN:
In this post, we review how caching works and dive into
Cache-Control HTTP headers, which are
used to configure how your content is cached across the internet and end-user
devices. We will explore how to configure them and what guidelines to follow
when using them.
Getting caching right can be tricky and getting it wrong is headache-inducing. Learning about it is even more critical now that over 50% of the internet's traffic is served towards mobile devices where interactions are highly latency-sensitive and long loading times will trigger high bounce rates.
Table of Contents
Caching technologies temporarily store data between origin servers and end-users to improve site performance and reduce latency for the requests of your web services and apps.
At a high level,
Cache-Control is an HTTP header that specifies caching policies for client requests and server responses. These policies, which are configured parameters, determine how resources are cached, where, and their time-to-live before expiring.
It is important to understand there are 3 main locations where content is cached:
There are 3 key benefits to caching:
Before diving into how to use
Cache-Control, it is helpful to review briefly the history of configuring cache.
With HTTP 1.0, caching headers were limited to:
Expires:This is an age limitation for cached content, and it is configured to a specific date and time. It is an absolute limit that is not relative to the client's request.
Expires: Mon, 22 Apr 2021 23:59:25 GMT
Pragma: no-cacheThis is a no-cache meta tag that does not necessarily keep a web page fresh.
The release of HTTP 1.1 addressed the limitations of caching with
Pragma: no-cache by adding a new class of headers,
Cache-Control. These new headers offer more control over their content than previously possible with HTTP 1.0.
Depending on your needs, you can use multiple
Cache-Control directives by separating each of them with a comma.
Cache-Control: public, max-age=60, stale-while-revalidate=180
Below is a non-exhaustive list of the different request and response directives you can use for
max-age - The maximum number of seconds a representation is considered fresh. This time limit is relative to the client's request whereas the time limit of
Expires is absolute. If both headers are used,
max-age takes priority over
max-age is an improvement from
expires given that it is a relative time-to-live for each request. The relativity of the
max-age directive mitigates for unintentional DDoS or spikes in traffic.
max-stale - The maximum number of seconds a client will accept a stale response. A stale response is a response that is older than the time specified by
min-fresh - The minimum number of seconds a client wants a response to be fresh. A cache is fresh as long as the amount of time of max-age has not expired.
no-cache - The stored response must be validated with the origin server before being used. This is a good option if important information on your site changes, but you still want the benefits of caching.
no-store - Blocks a cached copy from being stored anywhere, be it in a local cache, a browser, or third-party proxy. This directive forces each request to travel to the origin server to access the content. It is expensive and slower, but it does guarantee up-to-date content. Important note: this directive cannot be used with other directives.
stale-if-error- If there is an error during a check for a fresh response, this is the number of seconds the client will accept a stale response. If the number of seconds has not been surpassed,
stale-if-error returns a cached response.
private - The response can only be stored on a browser's response. Use this directive when the content is meant for a specific end-user. For example, an individual email account.
public - The response can be stored on any cache. Use this directive when anyone can see the content. For example, a product's pricing page.
no-cache - The stored response must be validated with the origin server before being used. This is a good option if important information on your site is updated, but you still want the benefits of caching.
no-store - Blocks a cached copy from being stored anywhere, be it in a local cache, a browser, or third-party proxy. This directive cannot be used with other directives.
max-age - The number of seconds after the initial request that a representation is considered fresh. This is relative compared to
Expires, which is absolute.
max-age takes priority over
max-age is an improvement from
expires given that it is a relative time for each request rather than an absolute expiration date shared across clients. The
max-age directive mitigates for unintentional DDoS or spikes in traffic and is indifferent towards the clocks or timezones for each client.
s-maxage - Like
max-age, but only for shared proxies, like CDN providers.
must-revalidate - Forces request to validate with the origin server.
proxy-revalidate - Like
must-validate but only for proxy caches, not private caches.
stale-while-revalidate - Used with
max-age, it is the number of seconds a client will accept a stale response.
When a client request comes in for content,
max-age is used to determine if the response can be cached content. If the
max-age time has expired,
stale-while-revalidate is used to determine if the request will accept stale content.
This directive strikes a balance between loading cached content quickly and ensuring fresh content. It is most often used with asynchronous background workers to check for fresh content.
Important note: you can only use this cache-control header with Chrome and Firefox. Other browsers will ignore this header and only use
stale-if-error- Returns a cached response if an error is encountered, regardless of the content's freshness.
Validators check changes between the cached copies and the files at the origin server. If the content is valid, the client's request does not need to be sent all the way to the origin server.
Last-Modified - Sent by the origin server in its response,
Last-Modified marks the last modification to the resource. This validator is often used alongside the
Last-Modified: Fri, 8 Jan 2021 11:32:14 GMT
If-Modified-Since - This is a conditional validator that is sent by the client to check when the file was last changed to determine if the cached content matches what exists at the origin server. If the cached content matches what exists at the origin server, the origin server returns a 304 to say it is ok to return the cached copy.
In addition to
Cache-Control headers, HTTP 1.1 also introduced a new validator:
ETag - The entity tag hashes a file byte by byte to create a digital fingerprint of the file. These unique identifiers determine if the cached content in a browser matches the content at the origin server.
Once a response, be it an ETag or conditional validator, is validated with the origin server, the origin server returns a
304 Not Modified response to the client. See illustration for more.
Caching is great for reducing latency and bandwidth costs, but what happens when you change or update content on one of your web pages? If that content is cached, you will need to find ways to invalidate those cached copies to serve your users your fresh and up-to-date content.
Cache invalidation is a way to deliver new content to your users by evicting an object from a CDN's cache. There are different methods to perform cache invalidation, the details of which vary across CDN providers.
This is easier said than done because there is an inherent trade-off for latency between performing
If-Modified-Since checks and serving potentially outdated cached content immediately.
Cache invalidation is usually reserved for special circumstances, as the best practice remains to configure a reasonable expiration time for your cacheable content with
s-maxage. Also it is helpful to keep in mind that higher
max-age times for
private cached content make it more difficult to update content cached in local browsers.
There are lots of options for configuring a
Cache-Control header. While there is not necessarily a standard, there are best practices that can guide you when configuring
Cache-Control for your websites.
You'll want to configure your
Cache-Control duration parameters depending on how frequently data is updated on the origin server. For resources that do not change so often, you could set the
max-age directive to 86400 seconds (equal to 24 hours) or even 31536000 seconds (equal to 1 year). In any case, another practice you'll want to keep in mind is keeping
private and browser caches short while setting larger time-to-live values for
public. Again, higher
private time limits make it difficult to update content cached in local browsers.
Below is a table we've prepared outlining best practices for real-world examples.
|Use Case||Best Cache Practice||Example Cache-Control|
|Static assets like images or fonts.||Public cache control to cache the information on proxy servers.||Cache-Control: public, max-age=31536000, immutable|
|Resources such as documents, images that are only available for one particular user or for authorized users.||Cache on browsers, not on proxy servers.||Cache-Control: private, max-age=3600|
|Content that changes, but not rapidly, and where multiple users will request the same content i.e., apps for news headlines or weather conditions.||Public cache to proxy servers with the possibility to fetch stale content.||Cache-Control: max-age=360, stale-while-revalidate=1200|
|Prevent returning errors if the origin server is unreachable when cache needs to be revalidated.||Use stale-if-error to continue serving the page resources beyond the time expiration of the resource.||Cache-Control: public, max-age=3600, stale-if-error=900|
|Dynamic content on social media feeds.||Never cache this data.||Cache-Control: no-store|
|Content that should be revalidated before being rendered.||Guarantee the freshest content without sending a new response every time.||Cache-Control: public, no-cache|
|Cached resources on your application deployed on Koyeb that require to be invalidated when a new version is deployed.||Cache at the edge as Koyeb invalidates the cache each time a new deployment occurs.||Cache-Control: s-maxage: 2592000|
Since CDNs store cached copies of your site and app in their network of data centers and proxy servers, CDNs shorten the distance between your users around the world and the server where your site or app is hosted on.
In addition to improving the latency experienced by your end-users, CDNs increase the performance of your sites and apps because gateway caching reduces the traffic that reaches the origin server.
All serverless deployments on Koyeb benefit from the platform's native global load-balancing and CDN. Traffic is routed through the nearest edge location for the end-user to reduce delivery latency.
Koyeb is a developer-friendly serverless platform that hosts web apps and services, Docker containers, APIs, event-driven functions, cron jobs, and more!
Thanks to native support of popular languages and built-in Docker container deployment, you can use Koyeb's serverless platform to deploy your projects.