HTTP compression: what to compress
Text shrinks to a fraction of its size; images, fonts and archives are compressed already, and a second pass only adds headers. This page lists what to send with gzip or Brotli, what to leave alone, and how to set it up.
By content type
| Content type | Compress? | Why | Left after gzip / Brotli |
|---|---|---|---|
| text/html | Yes | Tags and class names repeat, and Brotli finds common HTML words in its built-in dictionary. | 28.5% / 20.5% a pricing page |
| text/css text/javascript | Yes | Selectors, properties and keywords repeat, even after minification. | 46.0% / 36.2% minified JavaScript |
| application/json application/xml | Yes | Every record repeats the same keys. | 14.4% / 11.9% an API response |
| text/plain text/csv | Yes | Logs and tables repeat IP addresses, dates, paths and column values. | 8.9% / 7.1% an access log |
| image/svg+xml | Yes | SVG is XML text, full of repeated element and attribute names. | - |
| application/wasm | Yes | WebAssembly bytecode repeats instructions and names. | - |
| font/ttf font/otf | Yes | Plain font files are not compressed; WOFF2 is the better fix, but gzip helps meanwhile. | - |
| application/pdf | It depends | Most PDFs already compress their streams with DEFLATE, but some keep text plain. Try the file in the tool. | - |
| image/jpeg image/png image/webp image/avif image/gif | No | Image codecs compress the data already; PNG even uses DEFLATE inside. A second pass saves less than 1%. | - |
| font/woff2 font/woff | No | WOFF2 is Brotli inside and WOFF is zlib inside. | - |
| application/zip application/gzip | No | Archives are compressed already, and so are .docx, .xlsx, .jar and .apk, which are ZIP files. Compressing gzip again made our sample 4% larger. | - |
| video/* audio/* | No | Codecs such as H.264, AV1, AAC and Opus leave nothing for a general-purpose compressor. | - |
Server setup
nginx
gzip is built in; Brotli needs the ngx_brotli module. text/html is always compressed once gzip is on.
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_vary on;
gzip_types text/css application/javascript application/json
image/svg+xml text/plain application/xml application/wasm;
brotli on;
brotli_comp_level 5;
brotli_types text/css application/javascript application/json
image/svg+xml text/plain application/xml application/wasm;
# Serve app.js.br and app.js.gz, compressed at build time, when present.
brotli_static on;
gzip_static on;Apache
mod_deflate for gzip and mod_brotli (Apache 2.4.26 and later) for Brotli. List Brotli first so it wins when the browser accepts both.
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/css text/plain \
application/javascript application/json image/svg+xml
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/plain \
application/javascript application/json image/svg+xml
</IfModule>Caddy
encode compresses on the fly with gzip and zstd; Brotli comes from files compressed ahead of time.
example.com {
encode zstd gzip
file_server {
precompressed br gzip
}
}Static files: ahead of time
Compress at build time with Brotli 11 and gzip 9, and let the server send the .br or .gz file. It costs seconds once, and nothing per request.
Dynamic responses: on the fly
Brotli 4-6 or gzip 5-6. Higher levels take many times longer for a few percent, and the time is spent on every request.
Skip tiny responses
Under a few hundred bytes the 18-byte gzip frame and the CPU time eat the gain. A minimum length of 256 bytes is a common setting.
Send Vary: Accept-Encoding
A cache must keep the compressed and the plain response apart, or a client that can't decompress gets bytes it can't read. gzip_vary on adds the header in nginx.
Never compress twice
A response that is already a .gz file, an image or a ZIP gets nothing from another pass. Leave those types out of the list.
Check what the server sends
curl -sI -H "Accept-Encoding: br, gzip" https://example.com/app.js and look for content-encoding: br or gzip in the headers.