NGINX zstd compression delivers faster websites by compressing HTTP responses with the Zstandard algorithm. Want to improve your website’s performance? Enabling NGINX zstd compression is one of the most effective optimizations available today.
For decades, gzip has been the standard. However, a new contender has emerged: Zstandard (zstd) compression. This guide shows you how to enable NGINX zstd compression. You’ll also learn how to compare it with alternatives and optimize your configuration.
What is Zstandard (Zstd) Compression?
Zstandard, commonly abbreviated as zstd, is a fast lossless compression algorithm. Facebook (now Meta) developed it and released it in 2016. The algorithm provides compression ratios comparable to gzip. Additionally, it offers significantly faster compression and decompression speeds.
The key advantages of zstd over traditional compression algorithms include:
- Higher compression ratios than gzip at equivalent speed
- Faster compression than both gzip and Brotli
- Faster decompression leading to quicker page rendering
- Flexible compression levels ranging from 1 to 22
- Dictionary compression support for repetitive data
Browser Support for Zstd Content-Encoding
Before implementing NGINX zstd compression, understand which browsers support it. As of 2025, zstd enjoys broad support across major browsers:
| Browser | Version with Zstd Support |
|---|---|
| Chrome | 123+ (March 2024) |
| Firefox | 126+ (May 2024) |
| Edge | 123+ (March 2024) |
| Opera | 109+ (March 2024) |
| Safari | Not yet supported |
Safari doesn’t support zstd yet. However, this isn’t a problem in practice. When a browser lacks zstd support, NGINX automatically falls back to gzip or Brotli. As a result, all visitors receive compressed content.
Installing the NGINX Zstd Module
NGINX doesn’t include zstd support in its core distribution. Therefore, you need to install the nginx-module-zstd dynamic module. On RHEL-based distributions, the easiest way is through the GetPageSpeed repository. This includes RHEL, CentOS, Rocky Linux, AlmaLinux, and Fedora.
Prerequisites
First, ensure you have NGINX installed. Then install the zstd module:
dnf -y install nginx-module-zstd
Loading the Module
After installation, load the module in your NGINX configuration. Add these lines at the very top of /etc/nginx/nginx.conf:
load_module modules/ngx_http_zstd_filter_module.so;
load_module modules/ngx_http_zstd_static_module.so;
The installation provides two modules:
- ngx_http_zstd_filter_module – Compresses responses dynamically
- ngx_http_zstd_static_module – Serves pre-compressed
.zstfiles
Basic NGINX Zstd Compression Configuration
Here’s a minimal configuration to enable NGINX zstd compression:
http {
# Enable zstd compression
zstd on;
zstd_min_length 256;
zstd_comp_level 3;
zstd_types text/plain text/css text/xml text/javascript
application/json application/javascript
application/x-javascript application/xml
application/xml+rss application/atom+xml
image/svg+xml;
server {
listen 80;
server_name example.com;
root /var/www/html;
}
}
Let’s examine each directive:
zstd
Syntax: zstd on | off;
Default: zstd off;
Context: http, server, location
This directive enables or disables zstd compression for responses.
zstd_min_length
Syntax: zstd_min_length length;
Default: zstd_min_length 20;
Context: http, server, location
This sets the minimum response size for compression. Compressing very small responses wastes CPU cycles. The compression header overhead may exceed any size savings. Therefore, a value of 256 bytes works best for most use cases.
zstd_comp_level
Syntax: zstd_comp_level level;
Default: zstd_comp_level 1;
Context: http, server, location
This sets the compression level. Higher levels produce smaller files but require more CPU time. Valid values range from 1 to 22. However, values above 19 are typically only useful for offline compression. For dynamic compression, levels 1-5 offer the best balance.
zstd_types
Syntax: zstd_types mime-type ...;
Default: zstd_types text/html;
Context: http, server, location
This specifies MIME types to compress in addition to text/html. Include all text-based and JSON content types. However, skip binary formats like images and videos. They’re already optimized.
Complete Production Configuration
Here’s a production-ready NGINX zstd compression configuration. It passes security validation with Gixy:
load_module modules/ngx_http_zstd_filter_module.so;
load_module modules/ngx_http_zstd_static_module.so;
user nginx;
worker_processes auto;
worker_rlimit_nofile 4096;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Hide NGINX version for security
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# Gzip compression (fallback for browsers without zstd)
gzip on;
gzip_min_length 256;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript
application/x-javascript application/xml
application/xml+rss application/atom+xml
image/svg+xml;
server {
listen 80;
server_name example.com;
root /var/www/html;
# Zstd compression (preferred when client supports it)
zstd on;
zstd_min_length 256;
zstd_comp_level 3;
zstd_types text/plain text/css text/xml text/javascript
application/json application/javascript
application/x-javascript application/xml
application/xml+rss application/atom+xml
image/svg+xml;
# Serve pre-compressed .zst files when available
zstd_static on;
location / {
index index.html;
}
}
}
This NGINX zstd compression configuration ensures:
- Browsers supporting zstd receive zstd-compressed content
- Browsers without zstd support fall back to gzip
- Pre-compressed
.zstfiles are served when available - Security best practices are followed
Zstd vs Gzip vs Brotli: Performance Comparison
To understand the benefits of NGINX zstd compression, let’s examine actual benchmarks. The following tests used a 226 KB JSON API response. This represents typical modern web applications.
Compression Ratio Comparison
| Algorithm | Compressed Size | Reduction |
|---|---|---|
| Original | 226,388 bytes | – |
| Gzip (level 6) | 18,015 bytes | 92.0% |
| Zstd (level 3) | 9,486 bytes | 95.8% |
Zstd achieves nearly 50% better compression than gzip on this JSON payload. Moreover, it uses a lower compression level. This translates directly to faster page loads and reduced bandwidth costs.
Compression Speed Comparison
Speed matters for dynamic content. Here are the average response times:
| Compression | Response Time |
|---|---|
| None | 0.36 ms |
| Gzip (level 6) | 0.89 ms |
| Zstd (level 3) | 0.50 ms |
Zstd compresses 43% faster than gzip. Additionally, it produces significantly smaller output. As a result, your server handles more concurrent requests with less CPU overhead.
When to Use Each Algorithm
Based on testing and real-world deployment experience:
- Zstd – Best for dynamic content like HTML and JSON APIs. It offers the best balance of compression ratio and speed.
- Brotli – Best for static assets pre-compressed at build time. It achieves slightly better ratios but is much slower.
- Gzip – Universal fallback. Use it alongside zstd for full browser support.
Choosing the Right Compression Level
Zstd supports compression levels from 1 to 22. Here’s how different levels perform:
| Level | Compressed Size | Best For |
|---|---|---|
| 1 | 9,295 bytes | Maximum throughput |
| 3 | 9,486 bytes | Balanced (recommended) |
| 5 | 8,690 bytes | Moderate compression |
| 7 | 8,497 bytes | Higher compression |
| 9 | 7,912 bytes | Low-traffic sites |
| 12+ | 7,937+ bytes | Pre-compression only |
For dynamic NGINX zstd compression, use levels 1-5. Level 3 offers an excellent balance between compression ratio and CPU usage.
For static pre-compression, use higher levels. Compression happens only once:
zstd -10 -o style.css.zst style.css
zstd -10 -o app.js.zst app.js
Static Pre-Compression with zstd_static
For static assets, pre-compression offers the best of both worlds. You get maximum compression without runtime CPU overhead. Combined with proper browser caching, this delivers excellent performance.
Installing the Zstd CLI Tool
dnf -y install zstd
Pre-Compressing Files
Compress your static assets with a high compression level:
# Compress a single file
zstd -10 -f -o /var/www/html/css/style.css.zst /var/www/html/css/style.css
# Compress all CSS and JS files recursively
find /var/www/html -type f \( -name "*.css" -o -name "*.js" \) -exec zstd -10 -f {} \;
Configuring zstd_static
Enable the static module in your server or location block:
location /static/ {
zstd_static on;
gzip_static on; # Also enable gzip fallback
}
Syntax: zstd_static on | off | always;
Default: zstd_static off;
The always option forces zstd encoding regardless of client support. However, on is safer for production use.
How zstd_static Works
When a browser requests /static/style.css with Accept-Encoding: zstd:
- NGINX checks for
/static/style.css.zst - If found, NGINX serves it with
Content-Encoding: zstd - If not found, NGINX falls back to dynamic compression
Advanced Configuration Options
zstd_buffers
Syntax: zstd_buffers number size;
Default: zstd_buffers 32 4k;
Context: http, server, location
This controls memory allocation for compression buffers. The default works for most cases. However, increase it for very large responses:
zstd_buffers 64 8k;
zstd_dict_file
Syntax: zstd_dict_file file;
Context: http, server, location
This enables dictionary-based compression for smaller payloads. It’s an advanced feature requiring client-server coordination. Therefore, don’t enable it for general web traffic.
Combining Zstd with Gzip and Brotli
A robust production setup supports multiple compression algorithms. Here’s how to configure NGINX with all three:
http {
# Gzip (universal fallback)
gzip on;
gzip_min_length 256;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript
application/xml image/svg+xml;
# Brotli (if nginx-module-brotli is installed)
# brotli on;
# brotli_comp_level 4;
# brotli_types text/plain text/css text/xml text/javascript
# application/json application/javascript
# application/xml image/svg+xml;
server {
# Zstd (highest priority when supported)
zstd on;
zstd_min_length 256;
zstd_comp_level 3;
zstd_types text/plain text/css text/xml text/javascript
application/json application/javascript
application/xml image/svg+xml;
}
}
Compression Priority
When a browser sends Accept-Encoding: zstd, br, gzip, NGINX selects based on module loading order. With the setup above, zstd takes priority when supported.
Verifying NGINX Zstd Compression
After configuration, verify that NGINX zstd compression works correctly.
Testing with curl
# Request with zstd support
curl -s -I -H "Accept-Encoding: zstd" https://example.com/api/data
# Look for this header in the response:
# Content-Encoding: zstd
Measuring Compression Ratio
# Get compressed size
curl -s -w "%{size_download}" -o /dev/null -H "Accept-Encoding: zstd" https://example.com/page
# Get uncompressed size
curl -s -w "%{size_download}" -o /dev/null https://example.com/page
Testing Browser Behavior
Modern browsers handle Content-Encoding automatically. Open DevTools (F12) and go to the Network tab. Check the “Content-Encoding” response header for your resources.
Common Issues and Troubleshooting
Zstd Not Being Applied
Symptom: Responses don’t have Content-Encoding: zstd header.
Solutions:
- Verify modules are loaded using
nginx -T - Check that the content type is listed in
zstd_types - Ensure the response exceeds
zstd_min_length - Test client support with
curl -H "Accept-Encoding: zstd"
Module Load Errors
Symptom: NGINX fails to start with “unknown directive” errors.
Solutions:
- Place
load_moduledirectives at the very top of nginx.conf - Verify module files exist with
ls -la /etc/nginx/modules/ - Check module compatibility with your NGINX version
High CPU Usage
Symptom: Server CPU spikes after enabling zstd.
Solutions:
- Lower the compression level:
zstd_comp_level 1; - Increase minimum length:
zstd_min_length 1024; - Use static pre-compression for frequently accessed files
Performance Optimization Tips
1. Pre-compress Static Assets
For assets that don’t change often, pre-compress at build time:
# Add to your deployment script
find /var/www/html/static -type f \( -name "*.js" -o -name "*.css" -o -name "*.svg" \) \
-exec zstd -10 -f --rm {} \;
2. Skip Already-Compressed Formats
Don’t waste CPU on formats that won’t compress well:
location ~* \.(jpg|jpeg|png|gif|webp|woff2|mp4|webm)$ {
zstd off;
gzip off;
}
3. Use Appropriate Levels
Match compression level to content type:
# API responses (dynamic, need speed)
location /api/ {
zstd_comp_level 1;
}
# Static HTML (can tolerate more CPU)
location / {
zstd_comp_level 5;
}
4. Monitor Compression Ratio
Use the $zstd_ratio variable to log compression effectiveness:
log_format compression '$remote_addr - $request - '
'zstd_ratio: $zstd_ratio';
Conclusion
NGINX zstd compression represents a significant advancement over gzip. It offers superior compression ratios and faster compression speeds. As a result, zstd helps deliver faster websites while reducing bandwidth costs.
Key takeaways:
- Install the module with
dnf -y install nginx-module-zstd - Use level 3 for dynamic compression as a balanced default
- Pre-compress static assets with level 10 or higher
- Keep gzip enabled as a fallback for Safari and older browsers
- Monitor performance and adjust levels based on CPU capacity
By implementing NGINX zstd compression, expect 40-60% smaller payloads compared to gzip. This translates directly to faster page loads and better user experience.

