Skip to content

Instantly share code, notes, and snippets.

@Zodiac1978
Last active March 15, 2024 08:29
Show Gist options
  • Save Zodiac1978/3145830 to your computer and use it in GitHub Desktop.
Save Zodiac1978/3145830 to your computer and use it in GitHub Desktop.
Make your Website faster - a safe htaccess way
#
# Sources:
# http://stackoverflow.com/questions/7704624/how-can-i-use-gzip-compression-for-css-and-js-files-on-my-websites
# http://codex.wordpress.org/Output_Compression
# http://www.perun.net/2009/06/06/wordpress-websites-beschleuinigen-4-ein-zwischenergebnis/#comment-61086
# http://www.smashingmagazine.com/smashing-book-1/performance-optimization-for-websites-part-2-of-2/
# http://gtmetrix.com/configure-entity-tags-etags.html
# http://de.slideshare.net/walterebert/die-htaccessrichtignutzenwchh2014
# http://de.slideshare.net/walterebert/mehr-performance-fr-wordpress
# https://andreashecht-blog.de/4183/
#
<IfModule mod_deflate.c>
# Insert filters / compress text, html, javascript, css, xml:
# mod_deflate can be used for Apache v2 and later and is the recommended GZip mechanism to use
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/vtt
AddOutputFilterByType DEFLATE text/x-component
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/js
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/x-httpd-php
AddOutputFilterByType DEFLATE application/x-httpd-fastphp
AddOutputFilterByType DEFLATE application/atom+xml
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/ld+json
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/font-sfnt
AddOutputFilterByType DEFLATE application/x-web-app-manifest+json
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE font/sfnt
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
# Exception: Images
SetEnvIfNoCase REQUEST_URI \.(?:gif|jpg|jpeg|png)$ no-gzip dont-vary
# Drop problematic browsers
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
# Make sure proxies don't deliver the wrong content
<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
</IfModule>
# mod_gzip is an external extension and last updated 2015, so
# if available please use mod_deflate instead
# If you are stuck on Apache v1.3 you can use mod_zip to enable Gzip
# as mod_deflate is available for Apache v2 or later only.
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>
## EXPIRES CACHING ##
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 week"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rdf+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld+json "access plus 0 seconds"
ExpiresByType application/schema+json "access plus 0 seconds"
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
ExpiresByType image/x-icon "access plus 1 month"
ExpiresByType image/vnd.microsoft.icon "access plus 1 month"
ExpiresByType text/html "access plus 1 minute"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType text/x-javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 months"
ExpiresByType application/x-javascript "access plus 1 months"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/bmp "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
ExpiresByType text/plain "access plus 1 month"
ExpiresByType text/x-component "access plus 1 month"
ExpiresByType application/manifest+json "access plus 1 week"
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
ExpiresByType text/cache-manifest "access plus 0 seconds"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType font/eot "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType application/font-woff "access plus 1 month"
ExpiresByType application/font-woff2 "access plus 1 month"
ExpiresByType application/x-font-woff "access plus 1 month"
ExpiresByType font/woff "access plus 1 month"
</IfModule>
## EXPIRES CACHING ##
#Alternative caching using Apache's "mod_headers", if it's installed.
#Caching of common files - ENABLED
<IfModule mod_headers.c>
# 1 Month
<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
# 2 DAYS
<FilesMatch "\.(xml|txt)$">
Header set Cache-Control "max-age=172800, public, must-revalidate"
</FilesMatch>
# 2 HOURS
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "max-age=7200, must-revalidate"
</FilesMatch>
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|xml|gz|html|ttf)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>
# Set Keep Alive Header
# This *just* sets the header - maybe your hoster is not allowing this feature
# Please check if it is working with tools like http://www.webpagetest.org
<IfModule mod_headers.c>
Header set Connection keep-alive
</IfModule>
# If your server don't support ETags deactivate with "None" (and remove header)
<IfModule mod_expires.c>
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
</IfModule>
@fengler-it
Copy link

Works on two sites, but one site gives me an "Internel Server Error 500", when adding following line:

Header append Vary User-Agent env=!dont-vary
<<<

@Vvvetal-90
Copy link

PageSpeed Insights Errors:

/fonts/RobotoBold/RobotoBold.woff (expiration not specified)
/fonts/RobotoRegular/RobotoRegular.woff (expiration not specified)
/js/common.js (expiration not specified)
/js/libs.min.js (expiration not specified)

How it fix?

@Zodiac1978
Copy link
Author

@fengler-it These assets are all from an external server, so your change shouldn't have any impact.

@fengler-it I missed to add a check to this line. Is fixed now. Thanks for the heads up!

@Vvvetal-90 Woff is already compressed, no need to do it again, see: https://gist.github.com/Zodiac1978/3145830#gistcomment-1681099
About js files: Have you tried AddOutputFilterByType DEFLATE text/javascript?

@ginocremer
Copy link

ginocremer commented Jul 13, 2017

Hey Thorsten, my Javascripts weren't correctly cached, I think you should update your gist with the following (after that everything has been cached perfectly):

AddOutputFilterByType DEFLATE text/javascript

Edit: Sorry, just read your post above. Isn't it a good idea to update the gist anyway?

@Zodiac1978
Copy link
Author

@ginocremer I've added the line. Thanks for the reminder!

@bestfitbybrazil
Copy link

bestfitbybrazil commented Oct 15, 2017

Hi Zodiac1978. I just stepped into this and have been having trouble with fluctuating CPU performance jumping anywhere from 30% to beyond 77% in an instance. Not a whole lot of traffic when this is happening. This is my complete .htaccess file now.

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

<IfModule mod_expires.c>
# Enable expirations
ExpiresActive On 
# Default directive
ExpiresDefault "access plus 1 month"
# My favicon
ExpiresByType image/x-icon "access plus 1 year"
# Images
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
# CSS
ExpiresByType text/css "access plus 1 month"
# Javascript
ExpiresByType application/javascript "access plus 1 year"
</IfModule>
# END WordPress

i added the ifmodule mod_expires portion. can you give me complete i can use so as to cover everything else. Still getting low performance from test on gtMetrics. Here's message i get now.

There are 12 static components without a far-future expiration date.

https://fonts.googleapis.com/css?family=Poppins%3A300%2C400%2C500%2C600%2C700%7CLibre+Baskerville%3A400italic&subset=latin%2Clatin-ext&ver=4.8.2
https://bestfitbybrazil.com/wp-content/plugins/wp-spamshield/js/jscripts.php
https://www.livehelpnow.net/lhn/widgets/chatbutton/lhnchatbutton-current.min.js
https://apis.google.com/js/platform.js?onload=renderBadge
https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit2
https://www.google-analytics.com/analytics.js
https://translate.googleapis.com/translate_static/css/translateelement.css
https://translate.googleapis.com/translate_static/js/element/main.js
https://loader.wisepops.com/get-loader.js?v=1&user_id=2166
https://popup.wisepops.com/my-wisepop?uid=2166
https://translate.googleapis.com/translate_a/l?client=te&alpha=true&hl=en&cb=_callbacks____0j8stitkp
https://www.livehelpnow.net/lhn/scripts/livehelpnow.min.aspx?lhnid=19096&iv=0&ivid=0&d=0&ver=5.3&rnd=0.8954146259070273

The second "High priority" fix needed is:

Make fewer HTTP requests F (0) CONTENT HIGH
What's this mean?This page has 82 external Javascript scripts. Try combining them into one.This page has 39 external stylesheets. Try combining them into one.

@APlusDesign
Copy link

APlusDesign commented Nov 14, 2017

Why not combine some of the rules, for example.

#Alternative caching using Apache's "mod_headers", if it's installed.
#Caching of common files - ENABLED
<IfModule mod_headers.c>
	<FilesMatch "\.(ico|pdf|flv|swf|js|css|gif|png|jpg|jpeg|txt|html|htm)$">
		Header set Cache-Control "max-age=2592000, public"
	</FilesMatch>
	<FilesMatch "\.(js|css|xml|gz)$">
    	Header append Vary Accept-Encoding
  	</FilesMatch>
  	# Set Keep Alive Header
	# This *just* sets the header - maybe your hoster is not allowing this feature
	# Please check if it is working with tools like http://www.webpagetest.org
	Header set Connection keep-alive
</IfModule>

@Zodiac1978
Copy link
Author

@bestfitbybrazil 11 of 12 of these files are not on your server, but on 3rd-party-sites, so you cannot cache them with a server setting on your server. ;) The second one is on your server, but is dynamically generated (PHP file generating JS I assume). Not meant to be cached.

82 JS files + 39 CSS files are really a huge amount of files. You should definitely try to get rid of them. Caching is good, but doesn't solve the issue of having too much files loaded.

If you have reduced this to a smaller amount of files you can try to combine them with a tool like Autoptimize.

@Zodiac1978
Copy link
Author

@APlusDesign Nice idea, but I prefer the longer, more readable version. :)

@videikisairidas
Copy link

how to fix?!
The following cacheable resources have a short freshness lifetime. Specify an expiration at least one week in the future for the following resources:
http://..../wp-content/themes/Life/svg/f_discord1.svg

@Zodiac1978
Copy link
Author

This should not happen because of https://gist.github.com/Zodiac1978/3145830#file-htaccess-L63
Please check the MIME type the server is using for this SVG @PSAiridas

@mortensassi
Copy link

Thanks for sharing!
I'm using Wordpress with VueJS and i can't get the wp-json responses gzipped
Copied this htaccess to both my / where the Frontend is located and for /be where my wordpress is located

This is what pingdom is telling me:

The following cacheable resources have a short freshness lifetime. Specify an expiration at least one week in the future for the following resources:
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/footer-menu
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/header-menu
https://site.com/be/wp-json/wp/v2/projects
The following resources are missing a cache validator. Resources that do not specify a cache validator cannot be refreshed efficiently. Specify a Last-Modified or ETag header to enable cache validation for the following resources:
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/footer-menu
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/header-menu
https://site.com/be/wp-json/wp/v2/projects
The following publicly cacheable, compressible resources should have a "Vary: Accept-Encoding" header:
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/footer-menu
https://site.com/be/wp-json/wp-api-menus/v2/menu-locations/header-menu
https://site.com/be/wp-json/wp/v2/projects

@codeclinic
Copy link

mod_deflate is now the recommended option over mod_gzip.

@Zodiac1978
Copy link
Author

Hi @mortensassi this should be working if your host is using mod_deflate because of these lines:
https://gist.github.com/Zodiac1978/3145830#file-htaccess-L30-L31

@Zodiac1978
Copy link
Author

Hi @codeclinic - what do you recommend to change (as this gist is using mod_gzip and mod_deflate)?

@yashodhank
Copy link

yashodhank commented Jun 23, 2019

mod_gzip is legacy module Apache 1.3 and bellow.
mod_deflate is compatible with Apache 2.0 and above.

@Zodiac1978
Copy link
Author

@yashodhank Thanks for the explanation. I will add some more explanations as comments!

@wlarch
Copy link

wlarch commented Jul 16, 2020

Thanks for the Gist ! Very helpful.
In our case, we used the "modification" flag for the mod_expires.

Example :

ExpiresByType text/javascript "modification plus 1 year"

@Zodiac1978
Copy link
Author

Zodiac1978 commented Mar 26, 2021

Update: Added some more font mime types: font/sfnt, application/font-sfnt, font/otf and font/ttf

@Barnabas2
Copy link

If using CDN, will editing .htaccess have any effect? Since the request is served by the CDN and not the server?

@Zodiac1978
Copy link
Author

@Barnabas2 No, if the item is loaded from an external CDN the settings from your server (htaccess) have, of course, no effect.

@cutcool
Copy link

cutcool commented Jan 5, 2022

Hello, according to google page speed I don't have the compression of the following files enabled
…css/bootstrap.min.css , …remixicon/remixicon.css, …jquery/jquery.min.js and more but checking the compression in https://www.websiteplanet.com/es/webtools/gzip-compression/ compression is active ... why could google pagespeed not recognize compression?

@Zodiac1978
Copy link
Author

Zodiac1978 commented Jan 6, 2022

@cutcool I recommend to use https://www.webpagetest.org/ - this will give you more insights. If you use this .htaccess settings from above this shouldn't happen if those files are hosted on your server, because CSS and JS files should be compressed.

Are those files really hosted on your server and not from a CDN? Is your server using the mod_deflate module? You can check via phpinfo() (under "Loaded Modules"). (Or mod_gzip if you are on Apache <=1.3)

@monty88
Copy link

monty88 commented Sep 19, 2022

Hello, thank you for sharing that

But, is that work with WordPress ?

@Zodiac1978
Copy link
Author

@monty88 Yes, it is working with WordPress.

@twheel
Copy link

twheel commented Sep 3, 2023

Is there an advantage to caching using both mod_expires ( ExpiresByType...) and mod_headers (Header set Cache-Control...) or is it better to choose one or the other?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment