Site icon GetPageSpeed

NGINX Zstd Compression: Complete Configuration Guide

NGINX Zstd Compression: The Ultimate Guide to Next-Gen HTTP Compression

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:

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:

  1. ngx_http_zstd_filter_module – Compresses responses dynamically
  2. ngx_http_zstd_static_module – Serves pre-compressed .zst files

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:

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:

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:

  1. NGINX checks for /static/style.css.zst
  2. If found, NGINX serves it with Content-Encoding: zstd
  3. 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:

  1. Verify modules are loaded using nginx -T
  2. Check that the content type is listed in zstd_types
  3. Ensure the response exceeds zstd_min_length
  4. Test client support with curl -H "Accept-Encoding: zstd"

Module Load Errors

Symptom: NGINX fails to start with “unknown directive” errors.

Solutions:

  1. Place load_module directives at the very top of nginx.conf
  2. Verify module files exist with ls -la /etc/nginx/modules/
  3. Check module compatibility with your NGINX version

High CPU Usage

Symptom: Server CPU spikes after enabling zstd.

Solutions:

  1. Lower the compression level: zstd_comp_level 1;
  2. Increase minimum length: zstd_min_length 1024;
  3. 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:

By implementing NGINX zstd compression, expect 40-60% smaller payloads compared to gzip. This translates directly to faster page loads and better user experience.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version