Why is my site not using gzip or Brotli compression?

Text-based responses such as HTML, CSS, JavaScript and JSON can shrink dramatically before they leave your server, but only if compression is switched on and the client asks for it. If your responses arrive with no Content-Encoding header, every visitor is downloading the full, uncompressed bytes. This page covers how to enable gzip and Brotli compression across the common stacks and how to prove it is working.

Why this matters

Compression is a negotiation. The browser advertises what it understands with an Accept-Encoding request header, and the server answers with a Content-Encoding header naming the algorithm it used. When that handshake never happens, HTML, CSS, JavaScript, JSON, SVG and other text assets transfer at full size, which wastes bandwidth and slows first render, especially on mobile connections where transfer time dominates. On a responsive app that is a direct hit to the loading metrics users feel. There is one caveat to respect: both nginx and Apache warn that compressing responses over TLS can enable the BREACH family of attacks, which exploit compression alongside secrets in a response. The practical guidance is to compress ordinary static and public content while being careful not to compress responses that mix attacker-controlled input with sensitive secrets. Compression also does nothing for formats that are already compressed, such as JPEG, PNG, WebP and MP4, because their bytes are close to random and can even grow slightly when re-compressed, which is why servers limit gzip and Brotli to text-based types.

How to fix it

Enable compression closest to where your content is produced. nginx needs gzip on; because it is off by default, plus the MIME types you want compressed and a minimum length so tiny files are left alone. Apache uses the DEFLATE filter for gzip and the BROTLI_COMPRESS filter for Brotli. In Node, the compression middleware handles whole responses, and Next.js already compresses for you when it serves the app. Cloudflare handles it automatically at the edge.

# nginx
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;

# Apache: gzip via mod_deflate
SetOutputFilter DEFLATE

# Apache: Brotli via mod_brotli (2.4.26+)
SetOutputFilter BROTLI_COMPRESS

// Express: gzip, deflate and Brotli
const compression = require('compression');
const app = express();
app.use(compression());

// Next.js: gzip is on by default with next start; disable with compress: false
module.exports = {
  compress: false,
}

How to verify the fix

Request a page while advertising the encodings you support, then look for a content-encoding header on the response. A value of br means Brotli and gzip means gzip; if the header is missing, nothing was compressed. You should also see a vary: Accept-Encoding header so caches store the compressed and uncompressed versions separately. A missing vary: Accept-Encoding header is its own warning sign, because a shared cache may then hand a compressed copy to a client that never advertised support for it.

curl -sI -H "Accept-Encoding: gzip, br" https://example.com | findstr /I "content-encoding vary"

Automated check

The automated check requests your page with compression advertised and reports No compression enabled as a Low-severity finding when a compressible response comes back without a Content-Encoding header. It confirms whether compression is active rather than benchmarking the compression ratio, so pair it with a quick look at actual transfer sizes if bandwidth matters to you.

Scan your app free →

Related issues