In order to speed up my site Telium I created the .htaccess below. I have a lot of large images on my site so I want browsers to cache as much as possible. However, when I browse to my web site with the developer tools open (in Chrome), I see that all of the images on my pages are being re-downloaded every time I refresh.

My .htaccess is located in the root of the web site, and looks like this:

# BEGIN Expire headers
<ifModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 5 seconds"
  ExpiresByType image/x-icon "access plus 2592000 seconds"
  ExpiresByType image/jpeg "access plus 2592000 seconds"
  ExpiresByType image/png "access plus 2592000 seconds"
  ExpiresByType image/gif "access plus 2592000 seconds"
  ExpiresByType application/x-shockwave-flash "access plus 2592000 seconds"
  ExpiresByType text/css "access plus 604800 seconds"
  ExpiresByType text/javascript "access plus 216000 seconds"
  ExpiresByType application/javascript "access plus 216000 seconds"
  ExpiresByType application/x-javascript "access plus 216000 seconds"
  ExpiresByType text/html "access plus 600 seconds"
  ExpiresByType application/xhtml+xml "access plus 600 seconds"
# END Expire headers
<ifModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/xml text/css text/plain
  AddOutputFilterByType DEFLATE image/svg+xml application/xhtml+xml application/xml
  AddOutputFilterByType DEFLATE application/rdf+xml application/rss+xml application/atom+xml
  AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript application/json
  AddOutputFilterByType DEFLATE application/x-font-ttf application/x-font-otf
  AddOutputFilterByType DEFLATE font/truetype font/opentype
# BEGIN Cache-Control Headers
<ifModule mod_headers.c>
  <filesMatch ".(ico|jpe?g|png|gif|swf)$">
    Header set Cache-Control "public"
  <filesMatch ".(css)$">
    Header set Cache-Control "public"
  <filesMatch ".(js)$">
# was private  
    Header set Cache-Control "public"
  <filesMatch ".(x?html?|php)$">
    Header set Cache-Control "private, must-revalidate"
# END Cache-Control Headers

Here is an example of the response headers associated with an image retrieved every time I refresh:

Date:Thu, 28 May 2015 12:56:46 GMT
Expires:Sat, 27 Jun 2015 12:56:46 GMT
Last-Modified:Sat, 23 May 2015 12:11:09 GMT
Server:Apache/2.4.12 (Unix) OpenSSL/1.0.1e-fips mod_bwlimited/1.4 mod_fcgid/2.3.10-dev

Can someone suggest what I need to change to make the browser cache these images (and text)?

Update: After adding max-age the new header is:

Cache-Control:public, max-age=2592000
Date:Thu, 28 May 2015 20:11:33 GMT
Expires:Sat, 27 Jun 2015 20:11:33 GMT
Last-Modified:Sat, 23 May 2015 12:11:28 GMT
Server:Apache/2.4.12 (Unix) OpenSSL/1.0.1e-fips mod_bwlimited/1.4 mod_fcgid/2.3.10-dev

3 Answers 3


You should change...

Header set Cache-Control "public"


Header set Cache-Control "max-age=x"

...but replace x with the number of seconds you want any browser that supports HTTP 1.1 to cache the item. The reason why I removed "public" is because public is the default scope in which caching is applied to (meaning it is applied to browsers and proxies in-between).

The if-modified checks and etags as Richhallstoke suggests is useful but requests are still made to the server even to receive a 304 response. Caching with a maximum age set (whether through expires header or cache-control's max-age) means the browser will use the local cache as much as possible until the defined age is reached or a forced refresh is made. This means more bandwidth savings for the client.

  • I added the max-age and the updated response header is show above. Still chrome is going out to retrieve the file. (Is it possible that Chrome will ALWAYS retrieve the file when developer tools are open).
    – TSG
    May 28, 2015 at 20:22
  • I'm starting to think its something built into chrome. Have you tried other web browsers? May 28, 2015 at 21:06
  • @Telium only when the "Disable cache" checkbox is checked!
    – MrWhite
    May 31, 2015 at 0:42
  • Well the answer above helped me fix the settings so I'll accept it. But w3d is right - I had that checked off too!
    – TSG
    May 31, 2015 at 13:55

There are two options really:

  1. You can remove the Last-Modified and ETag headers then the web browsers won't attempt to see if there's a newer version available and will just trust the Expires date/time you have supplied.

  2. You can check if HTTP_IF_MODIFIED_SINCE matches the last modified datestamp or if HTTP_IF_NONE_MATCH matches the ETag and then return a HTTP/1.1 304 Not Modified header instead of whatever resource was requested.

    I'm not aware of a method to achieve this directly from in an Apache .htaccess configuration file however you can do this in PHP, see example source code in my answer here: weird results from tests when trying to serve a fast image download page… why?

Some people have strong opinion that its best to let the browser manage the caching, and just get rid of the headers (option 1) however if you change your website before the Expires datestamp then anyone accessing resources could see their old cached versions against new HTML content until hitting Ctrl+F5 to force a refresh of all files required by the page. Checking the headers (option 2) and only returning content if a newer version is available is how the HTTP protocol was designed to be used efficiently.

In times gone by when each resource request required a separate network connection and browsers would only use a maximum of 10 concurrent connections to the same server I can understand this could cause delays in page load times, however provided your webserver is configured to allow persistent connections then web browsers will chain resource requests together and send them one after the other very quickly on the same connection.


I think this is normal behaviour when you click refresh - you're telling the browser to grab a fresh copy of the resources from the server. If you want to see it working, click on a link to another page and then click a link that takes you back to the page you were on.

Edit: Screenshot from Chrome developer tools, see all the '(from cache)' entries.

Chrome developer tools

  • Interesting idea...so I tried it but I see it sill downloading all images
    – TSG
    May 29, 2015 at 0:40
  • Works okay for me, I've added a screenshot to my answer. This is after loading your homepage, clicking the 'Company' link, and then clicking the 'Home' link again. Note all the '(from cache)' lines. May 29, 2015 at 11:42
  • Webpage test shows a similar result: webpagetest.org/result/150529_KV_M3J - note the lack of image downloads in the repeat view. May 29, 2015 at 11:44
  • Ok - something strange is going on. I appreciate the screen shot - since that shows mine is very different. I NEVER see 'from cache'. Maybe I need to start a new question....but is there any obvious reason my browser would not use the cache?
    – TSG
    May 29, 2015 at 14:33
  • Note the "Disable cache" checkbox at the top of the screenshot.
    – MrWhite
    May 31, 2015 at 0:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.