How to Configure Cache-Control Headers in Apache

Each user’s browser makes use of a built-in cache to store downloaded objects, which can significantly speed up repeat visits to your website by loading from disk rather than the network. Here’s how to configure it in Apache.

How Does Caching Work?

The first time a user connects to your site, they will download all static resources necessary to render the page, including things like your logo. When they navigate to a new page, it will load your logo from memory rather than asking for it again, speeding up performance significantly and reducing the load on your web server in the process.

This is a client-side cache, but many sites will also make use of a Content Delivery Network, or CDN. A CDN is a network of servers that sit in front of your main web server, or “origin” server. This network caches your pages, increasing your maximum bandwidth, reducing the access latency, and greatly reducing the stress placed on your origin server. If you’d like to learn more about CDNs, you can read our guide on them here.

Cache-Control is a header that you can configure your web server to add to all outgoing requests, which will tell the browser and CDNs how to cache your content.

Certain pages should never be cached by shared caches like CDNs. Doing so will risk displaying one user’s personal information to others. As a general rule, if the page is going to be the exact same for all users, like your home page, you can cache it. If it shows confidential user info, you’ll want to blacklist it from your cache. Static resources, like CSS and images, can usually be cached for everyone, often for much longer.


The amount of time object spend in the cache is also important. Referred to as the Time-To-Live (TTL), the maximum age of your cached resources determines how long the object will stay in cache before being invalidated, and prompting the user to request a new object. For static resources that don’t change much, you can set very high TTL values, usually around two years. For things that you might want to update, you’ll want to set lower TTL values to prevent stale resources from being in the cache for too long.

You can always use versioned filenames to trigger a cache reload. If you release a new version of a CSS style sheet, you can name it styles-1.0.1.css, and the user’s browser (and any CDNs in front of it) will see it as a new file that needs to be redownloaded. Additionally, for some CDNs, you can issue manual invalidations to flush the existing cache without changing any filenames.

How to Use Cache-Control in Apache

Cache-Control has a few options:

  • public – May be cached by anyone, including browsers and CDNs. Use this for most static objects.
  • private – Contains sensitive data that cannot be cached by CDNs or reverse proxies. The user’s browser may cache it locally. Use this for most authenticated pages.
  • no-cache – Despite the name, it doesn’t disable caching. The browser may still cache the response for performance but must check with the origin server for updates before using it. Use this if you want the user to revalidate each time.
  • no-store – Disables caching entirely. Use this only for highly sensitive data that shouldn’t be sent twice.

Additionally, you can add the no-transform directive, which disables any conversions that may be done to the resource. For example, some CDNs compress images to reduce bandwidth. This directive disables that behavior.

In Apache, you’ll have to set this header manually using the Header set directive, like so:

Header set Cache-Control "max-age=84600, public"

The max-age value is set in seconds, for example, max-age=300 for a five-minute TTL, and max-age=63072000  for two years.

You can put this directive in the root of your configuration to apply site-wide, but a better method is to apply the settings depending on the type of file. For example, to set a high TTL for most static media, you can use a FilesMatch block:

<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$"> Header set Cache-Control "max-age=63072000, public"

If you want to blacklist a specific path from being cached by CDNs, you can use a Directory block:

<Directory "/private"> Header set Cache-Control "max-age=300, private"

Or simply match a single file:

<File "protected.html">
Header set Cache-Control "max-age=300, private"

The blocks with the more specific matches will take precedence over general regex matches, but you’ll want to verify everything is being set properly on the receiving end. You can check this from Chrome’s DevTools, under Network > Headers.

chrome devtools network tab

If you only have access to .htaccess configuration, you can still use directory matching by creating a new .htaccess file in each subdirectory.

RELATED: How to Find Your Apache Configuration Folder

Use Surrogate-Control to Modify CDN Behavior Directly

The Surrogate-Control header functions exactly like Cache-Control, but details specific instructions for CDNs and reverse proxies, rather than end users. This way, you can tell CDNs to do one thing, but send different directions to the browser.

You’ll have to set this header manually, in the same way as you set Cache-Control:

Header set Surrogate-Control "max-age=300, public"

You will definitely want to test with your CDN to verify that this works—Surrogate-Control is fairly new, and isn’t universal.