Developer Toolbox

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.

Open Gzip & Brotli Compression Try your own file and see both sizes.

By content type

12 groups
Content typeCompress?WhyLeft after gzip / Brotli
text/htmlYesTags 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
YesSelectors, properties and keywords repeat, even after minification.46.0% / 36.2%
minified JavaScript
application/json
application/xml
YesEvery record repeats the same keys.14.4% / 11.9%
an API response
text/plain
text/csv
YesLogs and tables repeat IP addresses, dates, paths and column values.8.9% / 7.1%
an access log
image/svg+xmlYesSVG is XML text, full of repeated element and attribute names.-
application/wasmYesWebAssembly bytecode repeats instructions and names.-
font/ttf
font/otf
YesPlain font files are not compressed; WOFF2 is the better fix, but gzip helps meanwhile.-
application/pdfIt dependsMost 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
NoImage codecs compress the data already; PNG even uses DEFLATE inside. A second pass saves less than 1%.-
font/woff2
font/woff
NoWOFF2 is Brotli inside and WOFF is zlib inside.-
application/zip
application/gzip
NoArchives 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/*
NoCodecs 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.