How To: Setting Cache Control Headers in Nginx or Apache

How To: Set Cache Control Headers in Nginx or Apache… The Right Way

Want to set some browser cache headers for static assets (like images) on your site the right way to get some tight scores on the likes of GTMetrix and WebPageTest?

The Nginx Method

location ~* ^.+\.(ico|css|js|gif|jpe?g|png|woff|woff2|eot|svg|ttf|webp)$ {
    expires 30d;
    add_header Pragma public;
    add_header Cache-Control "public, no-cache";

You’ll want to add any extensions you want to include within the ( ). You may also want to tinker with the expiry time. You can see above expires 30d will request the browser caches the resource for 30 days, but you may want it longer.

You may also be wondering about the no-cache directive. “But wait! I want to cache my resources. Why am I setting using no-cache?”, I hear you ask.

So let’s say you make a change to an image on your site that’s already cached by your visitor’s browsers from a previous pageview. They won’t see that for 30 days from the first day it was cached.

A lot of folks have suggested that using no-cache is a way to actually disable browser caching on those resources which wouldn’t be great – because you want caching for best performance So they suggest using must-revalidate instead of no-cache.

But they’re wrong.

Well, not wrong. It will work. But, the must-revalidate directive tells the browser that each of the resources must be rechecked and if the version on the server differs from the cached version, it’s freshly served to the browser.

However, in the nasty case that your server doesn’t respond to a revalidation request while using must-revalidate in your header (which is common if your web server is dealing with high traffic), the browser or proxy will return a 504 errors for those requests to your visitors. This can either manifest as those resources not loading, or as a full page 504 – depending on how your site is coded.

It’s somewhat misleading, but no-cache doesn’t actually “uncache” the resources like many people think.

What no-cache actually does is request revalidation from the server for the resource before serving it from the browser cache. If it hasn’t changed since the cached version, the browser will serve the cached version like with must-revalidate, but the difference in this case is your visitors don’t see any 504s – just the same old cached resource.

It means you still get to utilise browser caching, but also have updates to resources push out when they’re changed without the user having to clear their history or use incognito mode to see your modified content.

So to summarise, when using no-cache, the browser will just show the cached content if it gets a bad response, but also update if it gets a successful one. There are times to use must-revalidate of course, but this should be considered carefully with an experienced developer.

Apache (in .htaccess)

Often you won’t have access to modify Nginx configuration, though most web hosting providers will still add the above code to your site on request. Still, if you want a little more control over it you can also accomplish the same without modifying Nginx at all. You can add the following to your .htaccess file in the root of your instead:

<FilesMatch "\.(ico|css|js|gif|jpe?g|png|woff|woff2|eot|svg|ttf|webp)$">
Header set Cache-Control "max-age=604800, public, no-cache"

The subtle difference here is that the age is set in seconds with max-age=604800. 604,800 seconds is 30 days. But you can change that to whatever you like.

Here’s a handy breakdown to save you the math:

  • 1 hour: max-age=3600
  • 1 day: max-age=86400
  • 1 week: max-age=604800
  • 1 month: max-age=2628000
  • 1 year: max-age=31536000