How to Enable Brotli and Gzip Compression in Nginx on RHEL 7

Compression is one of the highest-return optimizations available for web servers: it requires minimal CPU investment but can reduce text-based response sizes by 60–80%, directly improving page load times and reducing bandwidth costs. Nginx ships with the ngx_http_gzip_module built in and available on every RHEL 7 installation, making gzip compression a zero-cost configuration change. Brotli, Google’s more efficient successor to gzip, requires an additional module — ngx_brotli — which must be either compiled from source or installed via a third-party package. This tutorial covers configuring gzip compression with all recommended directives, compiling and loading the ngx_brotli dynamic module, and verifying both compression methods work correctly using curl and browser developer tools.

Prerequisites

  • RHEL 7 server with sudo access
  • Nginx installed: sudo yum install -y nginx
  • For Brotli: Development tools and the libbrotli library (steps below)
  • curl available for compression testing
  • Internet access to clone the ngx_brotli repository from GitHub

Step 1: Configure Gzip Compression

Gzip is built into Nginx but disabled by default. All gzip directives live in the http block of /etc/nginx/nginx.conf (or an included conf.d file). Open the configuration file and add or modify the gzip block:

sudo vi /etc/nginx/nginx.conf

Add the following inside the http { } block:

http {
    # Enable gzip compression
    gzip on;

    # Minimum response body size to compress (bytes)
    # Responses smaller than this are sent uncompressed
    gzip_min_length 256;

    # Compression level: 1 (fastest, least compression) to 9 (slowest, most compression)
    # Level 5 is a good balance between CPU usage and compression ratio
    gzip_comp_level 5;

    # Send Vary: Accept-Encoding header so caches store separate gzip/non-gzip copies
    gzip_vary on;

    # Compress responses for proxied requests (requests with Via header)
    # "any" compresses regardless of the Via header value
    gzip_proxied any;

    # Number and size of buffers for compressed responses
    gzip_buffers 16 8k;

    # Minimum HTTP version for gzip (1.1 supports chunked encoding properly)
    gzip_http_version 1.1;

    # MIME types to compress — always include text/html; others listed here are common additions
    gzip_types
        text/plain
        text/css
        text/javascript
        text/xml
        text/x-component
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/xml+rss
        application/atom+xml
        application/vnd.ms-fontobject
        application/x-font-ttf
        font/opentype
        image/svg+xml
        image/x-icon;

    # Disable gzip for IE6 (known broken gzip support)
    gzip_disable "MSIE [1-6].";

    # ... rest of http block ...
}

Key Directive Explanations

  • gzip_comp_level 5: Levels above 5 give diminishing compression gains while consuming significantly more CPU. Benchmark your own workload if you need to tune this.
  • gzip_vary on: Critical for correctness with CDNs and reverse proxy caches. Without this, a cache might serve a gzip-compressed response to a client that doesn’t support gzip.
  • gzip_proxied any: Ensures compression is applied even when Nginx sits behind a load balancer that adds a Via header. Use off if you only want to compress direct client responses.
  • gzip_types: Note that text/html is always compressed and does not need to appear in this list. Never add image MIME types (jpeg, png, gif, webp) — binary images are already compressed and re-compressing them wastes CPU and can increase size.

Step 2: Test the Nginx Configuration and Reload

sudo nginx -t
# Expected: nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo systemctl reload nginx

Step 3: Verify Gzip Compression with curl

# Request a compressible resource with gzip in the Accept-Encoding header
curl -H "Accept-Encoding: gzip" -I http://localhost/

# Look for:
# Content-Encoding: gzip
# Vary: Accept-Encoding

# Download and compare sizes
curl -s http://localhost/ | wc -c
curl -s -H "Accept-Encoding: gzip" http://localhost/ --compressed | wc -c

# For a JavaScript or CSS file:
curl -v --compressed -H "Accept-Encoding: gzip" http://localhost/js/app.js 2>&1 | 
    grep -E "Content-Encoding|Content-Length|Vary"

If you see Content-Encoding: gzip in the response headers, gzip is working correctly. The --compressed flag tells curl to decompress the response body before displaying it, so you see readable content rather than binary data.

Step 4: Install Build Dependencies for ngx_brotli

Brotli compression requires the ngx_brotli module, which depends on the libbrotli C library. On RHEL 7, this library is not in the standard repos and must be installed from EPEL or compiled alongside the module.

sudo yum install -y epel-release
sudo yum install -y 
    gcc gcc-c++ make cmake 
    git 
    pcre-devel 
    zlib-devel 
    openssl-devel

# Check if libbrotli is available in EPEL
yum list available | grep brotli

# If available:
sudo yum install -y brotli brotli-devel

# If not available, the ngx_brotli module will build its own libbrotli
# from a git submodule — no separate install needed in that case

Step 5: Clone ngx_brotli and Compile as a Dynamic Module

You must compile ngx_brotli against the same Nginx version that is currently installed. Determine the version first:

nginx -v
# nginx version: nginx/1.20.1

NGINX_VERSION="1.20.1"  # Set to match your installed version

Clone the ngx_brotli repository and initialize its submodule:

cd /usr/local/src
sudo git clone --recurse-submodules -j4 
    https://github.com/google/ngx_brotli /usr/local/src/ngx_brotli

# Download the matching Nginx source
sudo wget "http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz"
sudo tar -xzf nginx-${NGINX_VERSION}.tar.gz
cd nginx-${NGINX_VERSION}

# Use --with-compat to build a dynamically loadable module
# that is ABI-compatible with the already-installed Nginx binary
sudo ./configure 
    --with-compat 
    --add-dynamic-module=/usr/local/src/ngx_brotli

sudo make modules

# Install the compiled modules
sudo cp objs/ngx_http_brotli_filter_module.so /usr/lib64/nginx/modules/
sudo cp objs/ngx_http_brotli_static_module.so /usr/lib64/nginx/modules/

# Verify the modules are in place
ls -la /usr/lib64/nginx/modules/ | grep brotli

Step 6: Load the Brotli Modules in nginx.conf

Add the load_module directives at the top of /etc/nginx/nginx.conf, before the events block:

# /etc/nginx/nginx.conf — top of file

load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

events {
    worker_connections 1024;
}

http {
    # Gzip config (from Step 1) ...

    # Brotli compression settings
    brotli on;

    # Compression level: 0 (no compression) to 11 (maximum compression)
    # Level 6 is recommended for dynamic content — balances ratio and CPU
    brotli_comp_level 6;

    # Enable static Brotli serving — serves pre-compressed .br files if they exist
    brotli_static on;

    # Minimum response size to compress with Brotli
    brotli_min_length 256;

    # Window size for compression (affects memory usage and compression ratio)
    brotli_window 512k;

    # MIME types to compress with Brotli (same list as gzip_types)
    brotli_types
        text/plain
        text/css
        text/javascript
        text/xml
        application/javascript
        application/x-javascript
        application/json
        application/xml
        application/xml+rss
        application/atom+xml
        image/svg+xml;

    # ... server blocks ...
}

Step 7: Test and Reload Nginx

sudo nginx -t
# Expected: nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo systemctl reload nginx

Step 8: Verify Brotli Compression with curl

# Request with Brotli in the Accept-Encoding header
# curl does not natively decompress Brotli, so inspect headers only
curl -v -H "Accept-Encoding: br,gzip" http://localhost/ 2>&1 | 
    grep -E "< Content-Encoding|< Vary"

# Expected output:
# < Content-Encoding: br
# < Vary: Accept-Encoding

# If the server returns gzip even when br is offered, confirm that:
# 1. The brotli modules loaded successfully (check error log)
# 2. The response MIME type is in brotli_types
sudo journalctl -u nginx --since "5 minutes ago" | grep -i brotli
sudo grep "brotli|emerg|crit" /var/log/nginx/error.log | tail -20

Step 9: Browser Verification

Modern browsers send Accept-Encoding: br, gzip, deflate automatically. To verify Brotli is being served to real browser clients:

  1. Open Chrome or Firefox DevTools (F12) and navigate to the Network tab.
  2. Load your page and click on any text or JavaScript resource.
  3. In the Response Headers panel, look for Content-Encoding: br.
  4. In the Request Headers panel, confirm Accept-Encoding: br, gzip, deflate, zstd was sent.

Step 10: Pre-compressed Static Files with brotli_static

For static assets with high traffic, pre-compressing files offline and serving the pre-built .br files eliminates per-request CPU overhead entirely. With brotli_static on, Nginx serves a pre-existing file.js.br when a client requests file.js with Brotli support.

# Install the brotli command-line tool
sudo yum install -y brotli  # or build from source if not in EPEL

# Pre-compress your static assets
cd /usr/share/nginx/html
for f in *.js *.css *.html; do
    brotli --quality=11 --output="${f}.br" "$f"
    echo "Compressed: $f → ${f}.br"
done

ls -lh *.br

Nginx will automatically select the .br file when a compatible client requests the original file — no URL changes or rewrite rules are needed.

Complete Compression Block Reference

http {
    # Gzip
    gzip               on;
    gzip_comp_level    5;
    gzip_min_length    256;
    gzip_vary          on;
    gzip_proxied       any;
    gzip_buffers       16 8k;
    gzip_http_version  1.1;
    gzip_types         text/plain text/css text/javascript application/javascript
                       application/json application/xml image/svg+xml;
    gzip_disable       "MSIE [1-6].";

    # Brotli
    brotli             on;
    brotli_comp_level  6;
    brotli_min_length  256;
    brotli_window      512k;
    brotli_static      on;
    brotli_types       text/plain text/css text/javascript application/javascript
                       application/json application/xml image/svg+xml;
}

Enabling both gzip and Brotli simultaneously is safe and recommended: Nginx negotiates the best format based on the client’s Accept-Encoding header, serving Brotli to modern browsers and gzip to older clients automatically. For typical web assets — HTML, CSS, JavaScript, and JSON API responses — the combination of gzip level 5 for compatibility and Brotli level 6 for modern clients delivers the best balance of compression ratio, CPU usage, and broad client support. Pre-compressing static assets with Brotli at level 11 offline for your highest-traffic files takes this a step further, effectively making compression free at request-serve time.