You enabled gzip compression in Nginx, but when you check the response headers, there's no Content-Encoding: gzip. Your pages still transfer at full size, slow to load, and bandwidth costs are high. The configuration looks correct, but compression just isn't happening.
Let's figure out why gzip isn't working and fix it.
Verify Gzip Is Actually Not Working
First, confirm the issue:
```bash # Check response headers curl -H "Accept-Encoding: gzip" -I http://localhost/
# Look for Content-Encoding: gzip # If missing, gzip is not working ```
Or use a detailed request:
curl -H "Accept-Encoding: gzip, deflate" -v http://localhost/ 2>&1 | grep -E "(Content-Encoding|Content-Length|Content-Type)"Compare compressed vs uncompressed size:
```bash # Uncompressed size curl -s http://localhost/ | wc -c
# Request with compression curl -s -H "Accept-Encoding: gzip" http://localhost/ --compressed | wc -c
# Or check the actual response size curl -s -H "Accept-Encoding: gzip" -w "%{size_download}\n" http://localhost/ -o /dev/null ```
Step 1: Check If Gzip Is Enabled
The most basic check—verify gzip is actually on:
# Check nginx.conf
grep -i gzip /etc/nginx/nginx.confYou should see:
gzip on;If gzip is missing or set to off:
http {
gzip on;
# Additional gzip settings...
}Apply changes:
nginx -t && systemctl reload nginxStep 2: Check Gzip Types
By default, Nginx only compresses text/html. If you're serving other content types, they won't be compressed:
http {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}Common MIME types to include:
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/atom+xml
image/svg+xml;Check what MIME type Nginx is returning:
curl -I http://localhost/your-file.js | grep -i content-typeIf Nginx returns application/x-javascript but you only have application/javascript in gzip_types, it won't compress.
Step 3: Check Gzip Min Length
Nginx won't compress small files by default:
# Default is 20 bytes, often too small
gzip_min_length 256;Check your file size:
# File size in bytes
curl -s http://localhost/your-file.js | wc -cIf smaller than gzip_min_length, it won't be compressed.
Step 4: Check Client Support
Gzip only works if the client sends Accept-Encoding: gzip:
```bash # Test with proper header curl -H "Accept-Encoding: gzip" -I http://localhost/
# Without the header (won't be compressed) curl -I http://localhost/ ```
Check what Nginx sees:
# Add to server block temporarily
location /debug {
return 200 "Accept-Encoding: $http_accept_encoding";
}Test:
curl -H "Accept-Encoding: gzip" http://localhost/debug
# Should show: Accept-Encoding: gzipStep 5: Check gzip_vary
The Vary: Accept-Encoding header helps caching proxies serve the right content:
gzip_vary on;Without this, a proxy might cache the uncompressed version and serve it to clients that want compression.
Step 6: Check gzip_proxied
If Nginx is behind a proxy or load balancer, compression might be disabled:
# Default is 'off' for proxied requests
gzip_proxied any;Options:
- off - Don't compress proxied requests (default)
- any - Compress all proxied requests
- expired - Compress if Expires header is set
- no-cache - Compress if Cache-Control has no-cache
- no-store - Compress if Cache-Control has no-store
- private - Compress if Cache-Control has private
- no_last_modified - Compress if no Last-Modified header
- no_etag - Compress if no ETag header
- auth - Compress if Authorization header present
Common configurations:
```nginx # For any proxied request gzip_proxied any;
# Or more selective gzip_proxied expired no-cache no-store private auth; ```
Step 7: Check Compression Level
The default level is 1, which is fast but less compression:
gzip_comp_level 6; # 1-9, higher = more compression but slowerTest different levels:
```bash # Level 1 (fastest) gzip_comp_level 1; # Typically 50% compression
# Level 6 (recommended balance) gzip_comp_level 6; # Typically 60-70% compression
# Level 9 (maximum) gzip_comp_level 9; # Typically 70-80% compression, but CPU intensive ```
Step 8: Check Buffer Settings
If gzip buffers are too small, compression might fail:
```nginx # Default: 4 4k or 8k buffers gzip_buffers 16 8k;
# Number and size of buffers # Total buffer space = number * size ```
Check the error log for buffer issues:
tail -f /var/log/nginx/error.log | grep -i gzipStep 9: Check for Conflicting Configurations
Multiple gzip configurations can conflict:
# Find all gzip settings
grep -r "gzip" /etc/nginx/Look for:
- gzip off; anywhere
- gzip settings in multiple places
- gzip disabled for specific locations
# This overrides http-level gzip settings
location /no-gzip {
gzip off;
}Step 10: Test with Compression Debugging
Enable debugging:
```nginx server { error_log /var/log/nginx/error.log debug;
location / { gzip on; gzip_min_length 256; gzip_types text/plain text/css application/json application/javascript; } } ```
Create a test endpoint:
location = /gzip-test {
default_type text/plain;
return 200 "This is a test string that should be compressed. We need at least 256 bytes to trigger compression. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
}Test:
```bash # Test compression curl -H "Accept-Encoding: gzip" -I http://localhost/gzip-test
# Should see: # Content-Encoding: gzip # Vary: Accept-Encoding ```
Complete Gzip Configuration
A production-ready gzip configuration:
```nginx http { # Enable gzip gzip on;
# Compression level (1-9) gzip_comp_level 6;
# Minimum file size to compress gzip_min_length 256;
# MIME types to compress gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml application/xml+rss application/atom+xml image/svg+xml;
# Proxied requests gzip_proxied any;
# Vary header for caching gzip_vary on;
# Buffer settings gzip_buffers 16 8k;
# HTTP version for gzip gzip_http_version 1.1;
# Disable for old IE versions gzip_disable "msie6";
server { listen 80;
# Optional: Disable for specific location location /api/binary { gzip off; proxy_pass http://backend; } } } ```
Brotli Alternative (Better Compression)
If you're using a modern Nginx, consider Brotli for better compression:
# Check if Brotli module is available
nginx -V 2>&1 | grep brotliIf available:
```nginx http { # Brotli (better than gzip) brotli on; brotli_comp_level 6; brotli_types text/plain text/css application/javascript application/json image/svg+xml; brotli_min_length 256;
# Also enable gzip for older clients gzip on; gzip_comp_level 6; # ... rest of gzip config } ```
Verification Checklist
After configuration:
```bash # 1. Test syntax nginx -t
# 2. Reload systemctl reload nginx
# 3. Test HTML compression curl -H "Accept-Encoding: gzip" -I http://localhost/ | grep -i content-encoding
# 4. Test CSS compression curl -H "Accept-Encoding: gzip" -I http://localhost/style.css | grep -i content-encoding
# 5. Test JavaScript compression curl -H "Accept-Encoding: gzip" -I http://localhost/script.js | grep -i content-encoding
# 6. Check compression ratio curl -s http://localhost/ | wc -c curl -s -H "Accept-Encoding: gzip" http://localhost/ --compressed | wc -c
# 7. Use online tool # Visit: https://www.giftofspeed.com/gzip-test/ ```
Common Issues Summary
| Issue | Symptom | Fix |
|---|---|---|
| Gzip off | No Content-Encoding header | gzip on; |
| Missing MIME type | Specific files not compressed | Add to gzip_types |
| File too small | Small files not compressed | Lower gzip_min_length |
| Proxied request | Behind CDN/load balancer | gzip_proxied any; |
| No Accept-Encoding | Client doesn't request gzip | Client issue, not Nginx |
| Wrong context | gzip in server/location | Move to http block |
| Conflict | gzip disabled elsewhere | Check all config files |
Gzip compression is essential for performance. Once working, you should see Content-Encoding: gzip in responses and significantly smaller transfer sizes. The typical compression ratio for text content is 60-80%, which translates to faster page loads and lower bandwidth costs.