The NGINX compression vary module is a drop-in replacement for the standard gzip_vary directive. It fixes a long-standing problem with how NGINX handles the Vary response header when compression is active.
When you enable compression in NGINX, the gzip_vary on directive adds Vary: Accept-Encoding to tell caches that the response differs based on encoding. However, this directive has a significant flaw: it does not merge with existing Vary headers from upstream applications. If your backend already sends Vary: Cookie, NGINX creates a second, separate Vary header instead of combining them. This breaks CDN caching and causes cache fragmentation.
The NGINX compression vary module solves this by merging, consolidating, and deduplicating Vary values into a single, clean header. It works with gzip, Brotli, and Zstandard compression.
The Problem with gzip_vary
Consider a reverse proxy setup where NGINX compresses responses from an upstream application. The upstream sends Vary: Cookie, and NGINX has gzip_vary on.
Here is what happens with the native gzip_vary directive:
$ curl -sI -H "Accept-Encoding: gzip" http://example.com/
Vary: Accept-Encoding
Vary: Cookie
NGINX outputs two separate Vary headers. While RFC 9110 allows multiple Vary headers, many CDNs handle this inconsistently. Some treat multiple Vary headers as a single list. Others ignore the second header or create separate cache entries for each combination.
The result is unpredictable behavior:
- Cache fragmentation — CDNs store more cache variants than necessary
- Cache misses — Proxies fail to match requests when
Varyis split across multiple headers - Duplicate values — Both upstream and NGINX add
Accept-Encoding
Multiple Upstream Vary Headers Make It Worse
Modern web applications often set several Vary values. An upstream might send:
Vary: Cookie
Vary: Accept-Language
Vary: Accept-Encoding
With gzip_vary on, NGINX adds yet another Vary: Accept-Encoding:
Vary: Accept-Encoding
Vary: Cookie
Vary: Accept-Language
Vary: Accept-Encoding
Four separate headers with a duplicate Accept-Encoding. This wastes bandwidth and confuses caches.
How the Module Solves This
The NGINX compression vary module is a header output filter. Instead of blindly adding a new Vary header, it does the following:
- Collects all existing
Varyheaders from the response - Parses them into individual values
- Removes duplicates using case-insensitive comparison
- Appends
Accept-Encodingonly if not already present - Outputs a single, consolidated
Varyheader
Using the same scenario from above, the module produces:
$ curl -sI -H "Accept-Encoding: gzip" http://example.com/
Vary: Cookie, Accept-Encoding
A single, clean header. For the multiple-header scenario:
$ curl -sI -H "Accept-Encoding: gzip" http://example.com/
Vary: Cookie, Accept-Language, Accept-Encoding
All values in one header with no duplicates.
How It Works Internally
The module registers itself as a header output filter in the NGINX filter chain. When a response passes through, it scans all outgoing headers for Vary entries. Each Vary header is tokenized by splitting on commas and whitespace. The tokens are collected into an array and compared case-insensitively to eliminate duplicates.
After processing, the module checks whether compression is active by reading the internal gzip_vary flag. This flag is set by NGINX’s built-in gzip module as well as by third-party modules like Brotli and Zstandard. If compression is active and Accept-Encoding is not already in the collected tokens, the module appends it.
Finally, all original Vary headers are removed from the response, and a single new Vary header is emitted with the deduplicated values separated by commas.
Supported Compression Modules
The NGINX compression vary module works with all major compression modules:
| Module | Directives |
|---|---|
| gzip (native) | gzip, gzip_static, gunzip |
| Brotli (third-party) | brotli, brotli_static, unbrotli |
| Zstandard (third-party) | zstd, zstd_static, unzstd |
You do not need separate configuration for each compression type. A single compression_vary on directive covers them all.
Installation
RHEL, CentOS, AlmaLinux, Rocky Linux, and Amazon Linux
Enable the GetPageSpeed repository, then install the module:
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-compression-vary
Load the module by adding this line at the top of /etc/nginx/nginx.conf:
load_module modules/ngx_http_compression_vary_filter_module.so;
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-compression-vary
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.
For more details, see the compression-vary module package page.
Configuration
The module provides a single directive.
compression_vary
Syntax: compression_vary on | off;
Default: compression_vary off;
Context: http, server, location
Enables or disables enhanced Vary header handling. When enabled, disable gzip_vary to avoid conflicts.
Basic Setup
A minimal configuration with gzip compression:
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript;
compression_vary on;
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
}
}
}
With Multiple Compression Algorithms
When using both gzip and Brotli:
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_types text/plain text/css application/json application/javascript;
compression_vary on;
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
}
}
}
One directive handles Vary for all compression modules simultaneously.
Per-Location Control
Enable or disable the module at different levels of the configuration:
http {
gzip on;
compression_vary on;
server {
listen 80;
location /api/ {
compression_vary off;
proxy_pass http://api-backend;
}
location / {
proxy_pass http://web-backend;
}
}
}
Testing Your Setup
Verify Configuration Syntax
sudo nginx -t
Reload NGINX
sudo systemctl reload nginx
Check the Vary Header
Test with a compressed request to verify correct behavior:
curl -sI -H "Accept-Encoding: gzip" https://example.com/ | grep -i vary
You should see a single, merged Vary header:
Vary: Cookie, Accept-Encoding
Multiple separate Vary headers mean the module is not active. Check for conflicting gzip_vary directives.
Test Without Compression
Verify that Vary is sent even without requesting compression:
curl -sI https://example.com/ | grep -i vary
The response should still include Vary: Accept-Encoding. Caches need to know the response could vary by encoding, even when the current response is uncompressed. This tells downstream caches to store the response separately based on the Accept-Encoding request header.
Why This Matters for CDN Performance
The Vary header controls how CDNs store and serve content. Getting it right directly improves cache hit rates and reduces origin server load.
Cache Key Construction
CDNs use Vary to construct cache keys. A Vary: Accept-Encoding header tells the CDN to store separate copies for gzip, Brotli, and uncompressed responses. This is correct and expected behavior.
However, duplicated or split Vary headers cause problems:
- Extra cache variants waste storage and reduce cache hit rates
- Failed cache matches increase origin load unnecessarily
- Ambiguous behavior varies across CDN providers
How CDNs Process Multiple Vary Headers
Different CDN providers handle multiple Vary headers differently. Cloudflare normalizes them into a single header, but not all providers do this. Fastly and AWS CloudFront may treat each Vary header independently when constructing cache keys, potentially creating unnecessary cache variants. By consolidating Vary headers at the origin, you remove this ambiguity entirely and ensure consistent caching behavior regardless of which CDN sits in front of your server.
Standards Compliance
RFC 9110 Section 12.5.5 defines the Vary header. The RFC recommends combining field values into a single header. The NGINX compression vary module ensures your responses follow this best practice automatically.
When to Use This Module
Use the NGINX compression vary module when:
- Your upstream sends
Varyheaders — Most frameworks (Django, Rails, Express, Laravel) setVary: Cookieby default - You use a CDN — Cloudflare, Fastly, KeyCDN, and CloudFront rely on
Varyfor caching decisions - You run multiple compression algorithms — Prevents duplicate
Accept-Encodingentries - You serve through a caching proxy — Varnish, NGINX
proxy_cache, or any HTTP cache benefits from clean, consolidated headers
The module has near-zero overhead. It processes only the Vary headers — no body processing, no additional I/O, no external dependencies.
Migrating from gzip_vary
Switching is straightforward:
- Install the module
- Replace
gzip_vary on;withcompression_vary on; - Remove all
gzip_varydirectives - Test with
nginx -tand reload
# Before
gzip on;
gzip_vary on;
# After
gzip on;
compression_vary on;
Both directives behave identically when there are no upstream Vary headers. The module adds value when upstream headers need merging. There is no downside to using it in all cases, and it future-proofs your configuration.
Troubleshooting
Multiple Vary Headers Still Appear
Check for gzip_vary on directives that conflict with compression_vary:
grep -rn "gzip_vary" /etc/nginx/
Remove all gzip_vary directives and reload NGINX.
No Vary Header at All
Verify that compression is enabled. The module only adds Accept-Encoding when a compression module is active. Ensure gzip on or brotli on is configured for the relevant location.
Unknown Directive Error
If nginx -t fails with unknown directive "compression_vary":
- Verify the
load_moduleline is at the top ofnginx.conf - Confirm the module file exists:
ls /usr/lib64/nginx/modules/ngx_http_compression_vary_filter_module.so
Conclusion
The NGINX compression vary module is a small but impactful upgrade over the native gzip_vary directive. It merges, consolidates, and deduplicates Vary headers so that CDNs and caching proxies handle compressed content correctly.
For production servers behind a CDN or caching proxy, switching from gzip_vary to compression_vary eliminates a common source of caching inefficiency with zero performance cost.
The module source code is available on GitHub. You can also check your website’s compression and caching headers with the GetPageSpeed speed checker.

