HomeWebGuides → nginx gzip

gzip on; and still
nothing is compressed.

You set gzip on;, reloaded, and your CSS is still shipping uncompressed. There are two reasons, both silent, and on a proxied application you usually have both at once. We found this on our own production server after years of it being wrong.

Found and fixed on our own server · August 2026 · by SPELL
text/htmlAll gzip on; covers
offgzip_proxied default
50%Transfer we cut
141KB→2.6KBOur own stylesheet

Confirm it in one command

Before changing anything, prove it. Ask for compression explicitly and look at the response headers:

curl -sI -H 'Accept-Encoding: gzip' https://example.com/assets/site.css \
  | grep -i content-encoding

If that prints content-encoding: gzip, you are fine. If it prints nothing, the file is going out raw. Do the same for your HTML — very often HTML is compressed and everything else is not, which is the tell for reason one.


Reason 1 — gzip on; only covers text/html

This surprises almost everyone. gzip on; enables compression, but the default value of gzip_types is text/html and nothing else. HTML is always compressed and cannot be removed from the list; CSS, JavaScript, JSON, SVG and XML are not included unless you say so.

So a config containing only gzip on; compresses your pages and none of your assets — which is exactly the pattern that makes people think gzip is working when it mostly is not.

The trap that hid it from us

Our nginx.conf shipped with the directives present but commented out — the Debian/Ubuntu default. A guarded edit that checked grep -q "gzip_types" matched the comment, concluded the setting already existed, and silently did nothing. If you script this, match on the uncommented form.


Reason 2 — gzip_proxied defaults to off

The second reason only bites if nginx is a reverse proxy — which it is for most Node, Python, Ruby, PHP-FPM and containerised applications.

gzip_proxied controls whether nginx compresses responses that came from an upstream. Its default is off, meaning proxied responses are never compressed regardless of what gzip_types says. Add the types but not this, and your app's responses stay raw while static files on disk compress fine — a genuinely confusing half-working state.


The configuration

In the http block:

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types text/plain text/css
           application/json application/manifest+json
           application/javascript text/javascript
           text/xml application/xml application/xml+rss
           image/svg+xml;

Then, and this matters: validate before reloading.

nginx -t && nginx -s reload

Keep a timestamped copy of the file first. On a box with more than one site on it, a bad reload takes every tenant down, not just the one you were working on.


What it was actually worth

Measured on this site, before and after, over the wire:

AssetRawGzippedSaved
Home page283 KB147 KB48%
Main stylesheet8.5 KB2.7 KB68%
Application JS22.6 KB8.3 KB63%
sitemap.xml2.6 KB0.45 KB82%
Total sampled338 KB166 KB50%

Half the bytes, from four lines of configuration that were sitting commented out. It is the highest ratio of benefit to effort available on most servers, and it is invisible until you go looking — which is why it survived on ours for years.

The related win, if you are already in there: check whether your stylesheet is carrying base64 fonts. Ours was 138 KB of which 94% was font data, all of it render-blocking. Extracting them to real .woff2 files with font-display: swap took the render-blocking payload to 2.6 KB.

We do this properly, on infrastructure you own

Server configuration, TLS, caching, compression and the measurement to prove any of it helped. Send us a URL and we will tell you what it is currently shipping.

Book a call →

More on this