Apache Compression, Vary, mod_deflate

Here is how I'm using Apache to compress my site content. Pretty simple but you do have to watch out for missing Content-Length headers, and incorrect handling of Accept-Encoding requests due to Vary header.

Deflate Lossless Compression

Deflate (stylized as DEFLATE) is a lossless data compression that uses a combination of LZ77 and Huffman coding. It was designed by Phil Katz, for version 2 of his PKZIP archiving tool. Deflate was later specified in RFC 1951 (1996).

Deflate Process

htaccess / httpd.conf Compression

Mainly using the AddOutputFilterByType directive with mod_deflate.

AddEncoding gzip svgz gz tgz
AddCharset utf-8 .js .css

SetEnvIfNoCase Request_URI \.(?i:gz|gif|jpe?g|png|webp|wav|mp3|flv|swf|ogg|ico|bmp|avi|mpg|pdf|woff2)$ no-gzip dont-vary

AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript
AddOutputFilterByType DEFLATE text/css text/html text/plain
AddOutputFilterByType DEFLATE text/xml application/xml application/atom+xml application/rss+xml application/json text/x-component application/xhtml+xml
AddOutputFilterByType DEFLATE application/ application/x-font-ttf font/opentype font/ttf image/svg+xml image/x-icon

<FilesMatch "\.(?i:js|css|x?html?|xml|txt|ttf|eot|otf|svg|woff)$">
  SetOutputFilter DEFLATE

<If "%{HTTP_HOST} == '' && '%{REQUEST_URI}' !~ m#\.(css|doc|eot|flv|gif|ico|jpe?g|js|json|mp3|otf|pdf|png|ppt|svgz?|swf|ttf|txt|wav|webp|wmv|woff2)$#i">
  Header edit Vary ^Accept-Encoding$ "Accept-Encoding,Cookie" env=!dont-vary

Serve pre-compressed files

Since mod_deflate re-compresses content each time a request is made, you can pre-compress the files and then have mod_deflate (or just serve normally using mod_headers) send those instead of having to recompress. This may be accomplished using a configuration like the following:

AddEncoding gzip .gz

SetEnvIfNoCase Request_URI \.gz$ no-gzip

AddType text/css .css.gz
AddType text/javascript .js.gz

<FilesMatch "\.js\.gz$">
  Header set Vary "Accept-Encoding"
  ForceType text/javascript

<FilesMatch "\.css\.gz$">
  Header set Vary "Accept-Encoding"
  ForceType text/css

Use mod_rewrite RewriteRule to serve pre-compressed

# Serve gzip compressed CSS and JS files if they exist and the client accepts gzip.
RewriteCond "%{HTTP:Accept-encoding}" "gzip"
RewriteCond "%{REQUEST_FILENAME}\.gz" -s
RewriteRule "^(.*)\.(css|js)"         "$1\.$2\.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]

<FilesMatch "\.gz$">
  # Serve correct encoding type.
  Header append Content-Encoding gzip
  # Force proxies to cache gzipped & non-gzipped css/js files separately.
  Header append Vary Accept-Encoding env=!dont-vary

Filters in Google PageSpeed module

Here is how the Google PageSpeed module uses filters:

Apache Environment Variables for Compression

There are a couple environment variables you can set with SetEnv or RewriteRule that control a couple aspects of compression.

If you have the DEFLATE filter activated, this environment variable will ignore the accept-encoding setting of your browser and will send compressed output unconditionally.
This causes any Vary fields to be removed from the response header before it is sent back to the client. Some clients don't interpret this field correctly; setting this variable can work around this problem. Setting this variable also implies force-response-1.0.
This forces an HTTP/1.0 response to clients making an HTTP/1.0 request. It was originally implemented as a result of a problem with AOL's proxies. Some HTTP/1.0 clients may not behave correctly when given an HTTP/1.1 response, and this can be used to interoperate with them.
When set to a value of "1", this variable disables the DEFLATE output filter provided by mod_deflate for content-types other than text/html. If you'd rather use statically compressed files, mod_negotiation evaluates the variable as well (not only for gzip, but for all encodings that differ from "identity").
When set, the DEFLATE filter of mod_deflate will be turned off and mod_negotiation will refuse to deliver encoded resources.
Disables brotli compression even if the client supports it.

Compression with Proxy Servers

The mod_deflate module sends a Vary: Accept-Encoding HTTP response header to alert proxies that a cached response should be sent only to clients that send the appropriate Accept-Encoding request header. This prevents compressed content from being sent to a client that will not understand it.

If you use some special exclusions dependent on, for example, the User-Agent header, you must manually configure an addition to the Vary header to alert proxies of the additional restrictions. For example, in a typical configuration where the addition of the DEFLATE filter depends on the User-Agent, you should add:

Header append Vary User-Agent

If your decision about compression depends on other information than request headers (e.g. HTTP version), you have to set the Vary header to the value *. This prevents compliant proxies from caching entirely.

Header set Vary *

Use Header always for cgi

You'll need to change the Header directives to use the always in order to have them show up in some cases. For example if you are using mod_proxy_fcgi with php-fpm, or some other cgi.

Header always append Cache-Control "no-transform" env=!no-gzip

More Info

Apache Optimization compression Htaccess httpd.conf mod_deflate