37

I have simple question. I have webdirectory /css and inside is file style.css. I have manually gzipped this file and saved it as style.css.gz. I want to save CPU cycles to not have CSS file compressed at each request. How do I configure Apache to look for this .gz file and serve it instead of compressing .css file over and over again ?

Note: I don't want Apache to create .gz file itself. In my scenario I have to create .css.gz file manually - using PHP on specific requests.

Frodik
  • 14,986
  • 23
  • 90
  • 141
  • I do something with PHP. On my CDN, Apache rewrites all js and css files requests to a php script which serves the compressed file if accepted by the browser or the minified file otherwise. It compares first the last modification time of both files (original & compressed), and only generates the compressed file if needed. If you use PHP and are interested I can paste it. – Nabab Feb 08 '12 at 21:50

6 Answers6

62

Some RewriteRule should handle that quite well.

In a Drupal configuration file I found:

# AddEncoding allows you to have certain browsers uncompress information on the fly.
AddEncoding gzip .gz

#Serve gzip compressed CSS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]

# Serve gzip compressed JS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]

# Serve correct content types, and prevent mod_deflate double gzip.
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1]

This will make the job. You can put that either in a <Directory foo/> section or in a .htaccess file if you do not have access to apache configuration or if you want to slowdown your webserver.

000
  • 3,976
  • 4
  • 25
  • 39
regilero
  • 29,806
  • 6
  • 60
  • 99
  • 9
    LOL - good comment about htaccess and slowing down webserver ;-) – Frodik Feb 06 '12 at 14:30
  • I've set up your solution, however waiting times for delivering resources (JS+CSS) are still high 50 - 100 ms in average so I am supposing that Apache is not using these rules or something is wrong, because when serving static files the waiting time seen in Dev tools in Chrome should be smaller, right ? – Frodik Feb 10 '12 at 13:23
  • check the request Accept-encoding is really gzip. The fact you may have several value in Accept-Encoding is maybe breaking it. Usually I use it with Varnish behind the server and this header is a simplier version of the variants: https://www.varnish-cache.org/trac/wiki/FAQ/Compression#Ithoughtyousaidthiswascomplicated . try to match the Accept-Ending RewriteCond with the one you have (`RewriteCond %{HTTP:Accept-encoding} gzip,deflate`) to sse if it comes from that problem – regilero Feb 11 '12 at 10:06
  • 4
    Old answer but still very useful. In my case (1and1 hosting) I had to change the `RewriteRule`s to `RewriteRule ^(.*)\.css %{REQUEST_URI}\.gz [QSA]` and `RewriteRule ^(.*)\.js %{REQUEST_URI}\.gz [QSA]`. Otherwise the server returned 404. Hope it helps somebody else. – rbarriuso Oct 24 '14 at 10:05
  • 1
    Tried to do both `.js` and `.json`; ended up appending the `$` end-of-string anchor on the Pattern side of RewriteRule: `RewriteRule ^(.*)\.js$ $1\.js\.gz [QSA]` ...otherwise the js rewrite caught the .json files and rewrote them to .js.gz rather than .json.gz. – leander Aug 17 '17 at 18:15
13

Continuing on regilero answer you should also add a few more lines in order to make sure the server answers with the corresponding Content-Encoding header:

#Serve gzip compressed CSS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]

# Serve gzip compressed JS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]

# Serve correct content types, and prevent mod_deflate double gzip.
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=is_gzip:1]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=is_gzip:1]
Header set Content-Encoding "gzip" env=is_gzip
tsilvestre
  • 173
  • 1
  • 5
  • Thanks for this addendum, I first tried using regilero's solution, but the files were returned without the `Content-Encoding` header and thus failed to work right. – Alex Aug 01 '15 at 13:02
  • 2
    http://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed describes something similar. They also mention `Header append Vary Accept-Encoding`, too. – MvG Jan 20 '17 at 18:54
9

Rather than implementing content negotiation yourself using mod_rewrite (and missing out on more advanced features like q-values, status 406, TCN, etc.) you may want to use mod_negotiation as discussed in this question. Copying my answer from there:

Options +MultiViews
RemoveType .gz
AddEncoding gzip .gz
<FilesMatch ".+\.tar\.gz$">
    RemoveEncoding .gz
    # Note:  Can use application/x-gzip for backwards-compatibility
    AddType application/gzip .gz
</FilesMatch>

This has the added bonus of working for all .gz files rather than just .css.gz and .js.gz and being easily extended for additional encodings.

It does have one major drawback, since only requests for files which do not exist are negotiated a file named foo.js would make requests for /foo.js (but not /foo) return the uncompressed version. This can be avoided using François Marier's solution of renaming uncompressed files with a double extension, so foo.js is deployed as foo.js.js. If this restriction isn't too painful for your deployment process, using mod_negotiation may be worth considering.

Community
  • 1
  • 1
Kevinoid
  • 4,180
  • 40
  • 25
  • Apache will automatically add headers used by `RewriteCond` to `Vary`: `If a HTTP header is used in a condition this header is added to the Vary header of the response`. http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritecond (see also "novary" flag) – mems Nov 02 '16 at 11:58
  • Good catch @mems. You are quite right, I stand corrected. I've removed the incorrect statement about the `Vary` header not being set properly. – Kevinoid Nov 02 '16 at 20:23
  • This is the answer that worked for me. Thanks! Great solution! – AKKAweb Dec 11 '17 at 17:19
7

I realise this isn't what you asked, but I'm throwing it out there anyway. Have you considered simply using mod_cache with mod_gzip? With the right configuration apache will gzip the files for you then pass the compressed cache files to clients and downstream proxies. This is way more efficient than what you propose in terms of time and effort and equally efficient in terms of bandwidth and computational load.

It doesn't matter that PHP has compressed the files first because with the right settings mod_gzip will detect that. The main point is you can cache compressed files and Apache won't recompress them.

SpliFF
  • 38,186
  • 16
  • 91
  • 120
3

I was struggling slightly with the proposed solution. For anyone else using VirtualHosts with different DocumentRoots you simply have to change the RewriteCond to:

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}\.gz -s
r3dDoX
  • 211
  • 3
  • 2
  • you are a genius. I was trying to serve pre-compressed content from my site's virtual host but couldn't; it would only work on the .htaccess file. Adding %{DOCUMENT_ROOT} to my RewriteCond was the key. Thank you so much. – GTS Joe Mar 15 '20 at 11:29
  • Yes you are a genius. There was no hint from Apache. The configuration looked fine but it was just not working until I added DOCUMENT_ROOT – Royalsmed May 06 '20 at 20:56
3

Isn't this blog post exactly the step by step you were looking for ?

We check that this gzipped version of the file exists (fourth line), and if it does, we append .gz to the requested filename

Bathz
  • 654
  • 4
  • 17
  • Yes, it looks very promising. But I am somewhat puzzled about Safari. The blog post is from 2007 and there is no more detail about Safari not working correctly with gzipped content. Is there any more detail about Safari and mainly if today's version works OK ? – Frodik Feb 07 '12 at 10:27
  • I cannot believe Safari do not handle this correctly in 2012. However, I'll leave you check that :). There is a bit of info there : http://webmasters.stackexchange.com/questions/22217/which-browsers-handle-content-encoding-gzip-and-which-of-them-has-any-special – Bathz Feb 07 '12 at 11:11
  • And it appears to me that the rules are quite the same regilero mentionned first. – Bathz Feb 07 '12 at 13:16