Apache + mod_deflate: serve pre-compressed content instead of deflating on each and every request, also serve webp if available.

This is a dump of a testing/beta apache vhost configuration that serves the following use-case:

  • Instead of gzipping html, js and css on each and every request, create gzipped files and serve them directly, without having apache compress the same file over and over again for each and every request.
    • index.html is requested, index.html.gz is being served instead.
  • When requesting a image/jpeg or image/png AND the browser sends “Accept: …image/webp…” then serve the webp-file instead of the requested jpeg.
    • foo.jpeg is requested, foo.jpeg.wep is being served.
  • do some default caching-header stuff.

One can use some content generators, triggers or hooks on a website-generator/cms to create/compress those files once and save bandwith/cpu on each and every request. The downside is, that those additional file do need precious disk-space too…

This is the vhost:

Define DOCROOT "/home/foo/www/www.example.com"

DocumentRoot ${DOCROOT}/
<Directory ${DOCROOT}/>
    AllowOverride None
    Options -Indexes +FollowSymLinks +Multiviews 
    Require all granted
</Directory>


#
# Prevent intermediate caches or proxies (e.g.: such as the ones
# used by mobile network providers) from modifying the website's
# content.
#
# https://tools.ietf.org/html/rfc2616#section-14.9.5
# https://developers.google.com/speed/pagespeed/module/configuration#notransform
#
<IfModule mod_headers.c>
    Header unset ETag
    Header set Cache-Control "private, no-transform"
</IfModule>
FileETag None

#
# Serve resources with far-future expires headers.
#
# (!) If you don't control versioning with filename-based
# cache busting, you should consider lowering the cache times
# to something like one week.
#
# https://httpd.apache.org/docs/current/mod/mod_expires.html
#
<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault                          "access plus 1 week"
    ExpiresByType text/cache-manifest       "access plus 0 seconds"
    ExpiresByType text/html                 "access plus 1 hour"
    ExpiresByType text/xml                  "access plus 1 day"
    ExpiresByType application/xml           "access plus 1 day"
    ExpiresByType application/json          "access plus 1 day"
    ExpiresByType application/rss+xml       "access plus 1 day"
    ExpiresByType application/atom+xml      "access plus 1 day"
</IfModule>

#
# Force compression for mangled `Accept-Encoding` request headers
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
#
<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
        RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
    </IfModule>
</IfModule>

#
# Since mod_deflate re-compresses content each time a request is made,
# some performance benefit can be derived by pre-compressing the
# content and telling mod_deflate to serve them without re-compressing
# them.
#
<IfModule mod_rewrite.c>
    AddEncoding x-gzip .gz

    #
    # Not all browsers accept compressed content, so only serve 
    # gzipped files if the browser can handle them. The uncompressed 
    # pages are used as a fallback, so these need to be kept.
    # The tricky bit here is to use LA-U, URL-based look-ahead, to 
    # ensure the actual file location is tested.
    # Do the rewrite if all the above conditions pass. For 
    # example, /1/gcc is rewritten to /1/gcc.gz. [L] stops the 
    # rewriting process.
    #
    RewriteEngine On
    RewriteCond %{HTTP:Accept-Encoding} gzip
    RewriteCond %{LA-U:REQUEST_FILENAME}.gz -f
    RewriteRule ^(.+)$ $1.gz [L]

    <FilesMatch "(.*\.html\.gz)$">
        ForceType text/html
        Header set X-Precompressed-Version-Served "*.html was requested, *.html.gz found and served"
    </FilesMatch>
    <FilesMatch "(.*\.css\.gz)$">
        ForceType text/css
        Header set X-Precompressed-Version-Served "*.css was requested, *.css.gz found and served"
    </FilesMatch>
    <FilesMatch "(.*\.js\.gz)$">
        ForceType text/javascript
        Header set X-Precompressed-Version-Served "*.js was requested, *.js.gz found and served"
    </FilesMatch>
</IfModule>

#
# When requesting a image/jpeg or image/png AND the browser 
# sends "Accept: ... image/webp ..." then serve the webp-file
# instead of the requested jpeg.
#
<IfModule mod_rewrite.c>
    AddEncoding image/webp .webp

    RewriteEngine On
    RewriteCond %{HTTP:Accept} image/webp
    RewriteCond %{LA-U:REQUEST_FILENAME}.webp -f
    RewriteRule ^(.+)$ $1.webp [L]

    <FilesMatch "(.*\.webp)$">
        ForceType image/webp
        Header set X-Webp-Replacement-Served "png/jpg was requested, *.webp replacement found and served"
    </FilesMatch>
</IfModule>