Improve Your YSlow Grade Using .htaccess

By  on  

This post was authored by Eric Wendelin. To learn more about Eric, click here.

A significant part of your YSlow grade depends on how well your site utilizes optimal caching techniques. By editing your .htaccess file, you can increase your YSlow score by 20 points or so in just 3 minutes!

Quick Intro to Caching

Caching is a browser feature that allows storage of certain types of web files on the client-side. In most cases, we want to have our clients cache our static files like HTML and CSS so that our website is faster after the first request.

Browser caching mainly depends on two things: the headers you send in an HTTP response and the browser your client is using.

304 Means wasted time

There are a couple of things a browser can do when caching depending on the headers used.

  1. It can check back on every request, and servers will reply with a HTTP status 304 (Not Modified) if the file is indeed the same.
  2. Only ask the server for the file when the cached one has expired.

The latter case is better for performance because the browser doesn't even ask and therefore saves a lot of HTTP requests.

Setting the Expires Header

One of the best things we can do to ensure good cache-ability is set a far future Expires header:

<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?$">
Header set Expires "Thu, 15 Apr 2012 20:00:00 GMT"
</FilesMatch>

Note here that if your site changes a lot, you'll need to rename/version your files or get clever with your ETags to keep users up-to-date. If you Firebug my site, eriwen.com, you'll see that I do just that. Stay tuned there for a script that can help you automate this.

Controlling Those ETags

ETags are difficult because they take precedence for caching in most browsers. You can change all the headers you want, but if the ETag associated with a file is always the same, caching will never work how you expect. In most situations, you should turn your ETag headers off.

<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?$">
Header unset ETag
FileETag None
</FilesMatch>

Yes, you can combine the two snippets:

<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?$">
Header set Expires "Thu, 15 Apr 2012 20:00:00 GMT"
Header unset ETag
FileETag None
</FilesMatch>

Testing Your New Settings

The best way to see what's going on is to check the "Net" tab in Firebug. You can use a tool like the Live HTTP Headers Firefox extension (there's also one for IE) to verify what headers are being sent. NOTE: If you refresh the page instead of clicking a link, Firefox will recheck all files it has cached. This is not the test you're looking for.

You want to make sure everything is occurring exactly as you intended. Now is the time to bring out your inner control-freak.

Conclusion

These are just 2 simple ways to maximize caching (and therefore speed) of your site. There are lots of other headers to play with, but I find that these two give the biggest bang for your buck. Share your caching tricks in the comments!

About Eric Wendelin

Eric Wendelin is a software engineer for Sun Microsystems. When he’s not doing super-secret programming for Sun, he plays indoor soccer, playing Wii with his friends, and cheering on the Colorado Avalanche. He also writes a blog on JavaScript, CSS, Java, and Productivity at eriwen.com

Recent Features

  • By
    From Webcam to Animated GIF: the Secret Behind chat.meatspac.es!

    My team mate Edna Piranha is not only an awesome hacker; she's also a fantastic philosopher! Communication and online interactions is a subject that has kept her mind busy for a long time, and it has also resulted in a bunch of interesting experimental projects...

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

Incredible Demos

  • By
    HTML5&#8217;s window.postMessage API

    One of the little known HTML5 APIs is the window.postMessage API.  window.postMessage allows for sending data messages between two windows/frames across domains.  Essentially window.postMessage acts as cross-domain AJAX without the server shims. Let's take a look at how window.postMessage works and how you...

  • By
    Fix Anchor URLs Using MooTools 1.2

    The administrative control panel I build for my customers features FCKEditor, a powerful WYSIWYG editor that allows the customer to add links, bold text, create ordered lists, and so on. I provide training and documentation to the customers but many times they simply forget to...

Discussion

  1. Thank you for hosting such a good article, Dave, another thing difficult to find a tutorial around is about gzipping resources (js, css) …any hint about this topic?

    I have googled around but nothing good came out: did you successfully try to gzip the site resources or do you know a good article about this?

    Thanks for your interesting work though!

    Stefano

  2. @Stefano: Yes, GZipping is the last step here.

    Here are a few lines from my .htaccess that WOULD do this if my hosting provider let me :(

    # Compress JS and CSS
    <Files *.js.gz>
    AddEncoding gzip .js
    ForceType text/javascript
    </Files>
    <Files *.css.gz>
    AddEncoding gzip .css
    ForceType text/css
    </Files>
    <IfModule mod_deflate.c>
    <FilesMatch "\.(js|css|html|jpg|jpeg|png|gif)$">
    SetOutputFilter DEFLATE
    </FilesMatch>
    </IfModule>
    
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule \.(gif|jpg|js|css)$ - [F]
    RewriteRule \.(css)$ - [F]
    RewriteRule ^(.*)\.css $1.css.gz [QSA]
    RewriteRule ^(.*)\.js $1.js.gz [QSA]
    </IfModule>
    
    • I have added this in my htaccess file.

      ExpiresActive On
      ExpiresDefault A1209600
      
      # 2 weeks
      ExpiresDefault A1209600
      Header append Cache-Control "public"
      
      
      # 2 hours
      ExpiresDefault A7200
      Header append Cache-Control "proxy-revalidate"
      
      
      # 3 days
      ExpiresDefault A259200
      Header append Cache-Control "proxy-revalidate"
      
  3. @Stefano: Yes, Gzipping is the last step here.

    I tried to post code for it, but it was eaten by David’s spam protector :(

    My hosting provider doesn’t allow Gzipping so you might check with yours if you’re unsuccessful.

  4. Interesting article, I didn’t know ETags issues at all! I’ll take a look to gzipping too.
    Thx Dave and Eric.

  5. Wow I didn’t even know there was two types of caching! Cool article Eric!

  6. @eric

    it is not advised to deflate binary content like images

  7. Monkeytail

    Technique works nice. Thanks!
    Websitewent from a 51 score to 74 points

  8. @xrado: Good note. If anyone uses my GZip stuff above remove the binary extensions like jpg, png, etc.

    @Monkeytail: There you go! Nice!

  9. My blog’s speed has improved a lot from this script. Thanks Eric!

  10. thank you very much for that!

    but i work with a lot with dynamic generated images (ie. .png?scale=200×400)
    so i modified your regex to match also uri’s with parameters:

    \.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(\.gz)?(\?.*)?$

    HTH

  11. @Michael:
    You’re entering the asteroid field there. It’s uncharted. IIRC IE will never cache anything with a query string. I can’t say for certain what FF or the others will do. If you know more about this I’d love to hear it!

  12. This is my .htaccess snippet that I use on all my sites that run on servers that support it:

    ################ Expires Control ################
    ExpiresActive On
    ExpiresDefault A0
    <FilesMatch “\.(gif|jpg|jpeg|png|swf)$”>
    # 2 weeks
    ExpiresDefault A1209600
    Header append Cache-Control “public”
    </FilesMatch>
    <FilesMatch “\.(xml|txt|html)$”>
    # 2 hours
    ExpiresDefault A7200
    Header append Cache-Control “proxy-revalidate”
    </FilesMatch>
    <FilesMatch “\.(js|css)$”>
    # 3 days
    ExpiresDefault A259200
    Header append Cache-Control “proxy-revalidate”
    </FilesMatch>

  13. If i paste the code given here on my .htaccess file i get a 500 error after refreshing

    • Hello Makis
      When you copy and paste the code form Here your code is getting changed specially for the closing double quotation. In respect with all replies regarding how to fix your web server and running HTTP server are wrong.

      Try to replace the “…” start and end double quotation with your hand and it will work. because when you copy and paste it it turned to ؛؛…؛؛ and you cannot notice that.

  14. @Makis: Your server may not have some of the functionalities above enabled or installed.

  15. @Makis: David is right. I’m sure you’ve double-checked that everything was copied perfectly, too. Some hosts might not allow this functionality

  16. Thanx for the fast replies.
    I m on a dedi server so i can change whatever is possible.
    Do you know how can i enable these functions?

    Thank you once more!

  17. @Makis: Which HTTP server are you running?

  18. Hi Eric!
    I m using Apache: 2.2.9

  19. @Makis:
    Try adding or un-commenting this in your httpd.conf file:

    LoadModule headers_module modules/mod_headers.so
    AccessFileName .htaccess

    Hope that helps :)

  20. @Makis:
    Try adding or un-commenting this in your httpd.conf file:

    LoadModule headers_module modules/mod_headers.so

    AccessFileName .htaccess

  21. Pierre

    Hi Eric,

    i’ve seen with YSlow that you are able to deflate “js” file with parameter (eg. http://davidwalsh.name/wp-includes/js/tw-sack.js?ver=1.6.1). On my dev site i can do this only with js without parameter. As soon as i have a parameter with my javascript file the deflate compression doesn’t works :( .

    I use this syntax (but i’ve tried too, the code from Michael without any success)

    <IfModule mod_deflate.c>
    <FilesMatch “\.(js|css|htm|html|pdf)$”>
    SetOutputFilter DEFLATE
    </FilesMatch>
    </IfModule>

    Could you help me with that weird problem ?

    TIA,

    Pierre.

  22. Rolf

    Good article..

    So, what do we do when we setup caching methods like this (e.g. for images) and you replace an image (jpg) on a website with a newer version (let’s say a better cropped one for example). Do I have to wait 2 weeks (if I set ExpireDate to that) before I see the new picture on the site, or can I somehow force the browser of the client to d/l the new version (even without adding a @timestamp to the image src=”” in the code)…?

    Versioning js/css files seems like an option, though a lot of work (renaming, updating files that include the versioned file, etc.).. but they do change a lot for projects usually.

    Rolf

  23. Pierre

    Ok, i found by myself…
    It’s because i use GoogleGears so YSlow was a little missed :).

    Thanks anyway

    Amicably,

    Pierre

  24. @Rolf: In your case I think it’s best to rename the file.

  25. I’ve posted this with improvements for gzipping and cache-control to GitHub: http://gist.github.com/188105

  26. kasap

    I have a question:
    Setting off Etags is going to affect the grade on the “Configure entity tags (ETags)” category on Yslow?

  27. @kasap: Yes. ETags being off will improve your YSlow score. ETags should be turned off unless you really know how to use them.

  28. Great optimization tip. I’m going to try it straight away.

    Thanks Eric.

  29. Dave

    Hi,
    The code for .htaccess contains – Header set Expires “Thu, 15 Apr 2012 20:00:00 GMT” which means that all the images will expire on 15th April 2012 at 20:00:00 .
    But how can i change the expire date to something like the cache shud become stale always after 14days. i mean i want to implement something like this ->

    $expires = 60*60*24*14;
    header("Cache-Control: maxage= $expires");
    header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
    

    How can i do this?

    One more thing, setting Expires is enough, or we also need to set Cache-Control, becoz Expires is something related to HTTP/1.0 and Cache-Control is related to HTTP/1.1

    Thank You,
    Dave

  30. Thanks for this nice article. – I improved my Yslow Grade with your tipps!

    Many thanks from Germany.

    Michael

  31. Thanks very much for giving such a great detail..!!
    I’m really impressed by this way..!!
    It reduces load time of my site really up-too great extent..!!
    Thanks..!!

  32. These snippets seem to be worth placing on the sites but I am so new to this I haven’t any ides of where they would be placed, sounds dumb ha, tell me where would they go and how to be placed.

  33. My blog loading speed has increased, thank you very much.

  34. thanks for the info. It moved my grade up 1 point, but everything helps.

  35. After going over a number of the articles on your blog, I really like your technique of blogging.
    I book marked it to my bookmark website list and will be checking back
    in the near future. Take a look at my web site too and tell me how you feel.

  36. This perhaps could make a VERY SMALL difference.

    You need to min all your JS and put it in the footer. Min all your CSS and put it in the header

    Make sure your site is gzipped.

    You do those 3 things and your actually going to see a difference. For example this blog has 28 JS files in the head of the document tsk tsk tsk

  37. thank you so much for the nice explanation,

  38. Why using W3TC my test results are still F
    Whereas the existing htaccess settings similar to those you share

  39. One more thing, setting Expires is enough, or we also need to set Cache-Control, becoz Expires is something related to HTTP/1.0 and Cache-Control is related to HTTP/1.1

  40. Getting error on yslow that “Add Expires headers”. How can i add for external urls?

  41. Thanks for the summary. These tips work exceptionally well for a Magento installation. We implemented these tweaks recently and saw a nice boost in speed.

  42. I have used the code to disable ETag, but does not function

  43. merans

    Hope this helps!

    RewriteEngine On
    # BEGIN Mod Header
    
    ExpiresActive On
    # Turn on Expires and set default expires to 10 years
    # END Mod Header
    
    # BEGIN Cache Control
    
    Header set Expires "Thu, 15 Apr 2012 20:00:00 GMT"
    Header unset ETag
    FileETag None
    
    #END Cache Control
    
  44. After read this, my speedtest result show C. Thanks

  45. Increase page loading speed

    # Turn on Expires and set default to 0
    ExpiresActive On
    ExpiresDefault A0
     
    # Set up caching on media files for 1 year
    
    ExpiresDefault A29030400
    Header append Cache-Control "public"
    
     
    # Set up caching on media files for 1 week
    
    ExpiresDefault A604800
    Header append Cache-Control "public"
    
     
    # Set up 1 week caching on commonly updated files
    
    ExpiresDefault A604800
    Header append Cache-Control "proxy-revalidate"
    
     
    # Force no caching for dynamic files
    
    ExpiresActive Off
    Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
    Header set Pragma "no-cache"
    
    
    
    
    
       
    	AddType application/x-javascript .js
    	AddType text/css .css
       
       
    	AddOutputFilterByType DEFLATE text/css application/x-javascript text/x-component text/html text/plain text/xml application/javascript
    	
    		BrowserMatch ^Mozilla/4 gzip-only-text/html
    		BrowserMatch ^Mozilla/4\.0[678] no-gzip
    		BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    	
        
        Header append Vary User-Agent env=!dont-vary
    		
    
    KeepAlive On
    MaxKeepAliveRequests 60
    KeepAliveTimeout 3
    
    Header set Expires "Thu, 15 Apr 2018 20:00:00 GMT"
    Header unset ETag
    FileETag None
    
  46. I’ve tried several variations of updating my ETags, but when I check my Yslow grade it doesn’t change. I did go from a D to a C initially, but I seem to be at a stand still now. Just curious if anyone else is running into the same issue.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!