Brotli compression delivers superior compression ratios compared to gzip, making it ideal for serving web content. However, what happens when your storage backend already has Brotli-compressed files, but some clients cannot decompress them? The unbrotli module solves this problem by transparently decompressing Brotli-encoded responses for clients that lack Brotli support.
What Is the Unbrotli Module and Why Do You Need It?
The unbrotli module is an NGINX response filter that decompresses content with Content-Encoding: br header. This filter enables a powerful optimization strategy: store your static assets in Brotli-compressed format to save disk space and I/O bandwidth, while automatically decompressing them for clients that only support gzip or no compression.
If you’re already using Brotli compression on NGINX, the unbrotli module is the perfect complement. It ensures that your Brotli-optimized storage strategy works for all visitors regardless of browser capabilities.
This approach is particularly valuable for:
- CDN edge storage: Keep one compressed copy, serve to all clients
- Object storage backends: Reduce storage costs with pre-compressed files
- Reverse proxy caches: Store Brotli-compressed responses from upstream
- Hybrid client environments: Support both modern browsers and legacy systems
Without this response filter, you would need to store multiple versions of each file (uncompressed, gzip, and Brotli) or reject requests from clients without Brotli support. The unbrotli module eliminates that complexity.
How the Module Works
This filter operates within NGINX’s response processing chain. When a response arrives with Content-Encoding: br, the unbrotli module checks whether the client supports Brotli by examining the Accept-Encoding request header.
If the client advertises Brotli support (Accept-Encoding: br), the response passes through unchanged. If the client does not support Brotli, the response filter decompresses the content on-the-fly before sending it to the client.
The Decompression Process
Here is what happens when the filter processes a response:
- NGINX receives a Brotli-compressed response from upstream (or static file)
- The filter checks the client’s
Accept-Encodingheader - If no Brotli support is found, decompression begins
- The
Content-Encoding: brheader is removed from the response - The
Content-Lengthheader is cleared (response becomes chunked) - The
ETagis converted to a weak validator (prefixW/added) - Decompressed content streams to the client
This process is automatic and transparent. Clients receive the content they can handle without any manual intervention or special configuration.
Installing the Module
RHEL, CentOS, AlmaLinux, Rocky Linux
Install the GetPageSpeed repository and the module package:
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-unbrotli
Then load the module in your NGINX configuration. Add this line at the top of /etc/nginx/nginx.conf, before the events block:
load_module modules/ngx_http_unbrotli_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-unbrotli
On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.
For detailed package information, see the unbrotli module page for RPM-based systems or the APT module page for Debian/Ubuntu.
Configuration Directives
The unbrotli module provides three configuration directives. All directives work in http, server, and location contexts.
unbrotli
Syntax: unbrotli on | off;
Default: off
Context: http, server, location
This directive enables or disables Brotli decompression. When enabled, the filter decompresses responses with Content-Encoding: br for clients that do not include br in their Accept-Encoding header.
location /static/ {
unbrotli on;
proxy_pass http://storage-backend;
}
unbrotli_force
Syntax: unbrotli_force on | off;
Default: off
Context: http, server, location
This directive forces decompression regardless of client capabilities. When enabled, all Brotli-compressed responses are decompressed, even if the client supports Brotli.
Use this directive for:
- Debugging compression issues
- Ensuring consistent response format for processing
- Testing decompression without modifying client headers
location /debug/ {
unbrotli on;
unbrotli_force on;
proxy_pass http://storage-backend;
}
unbrotli_buffers
Syntax: unbrotli_buffers number size;
Default: 32 4k or 16 8k (depends on system page size)
Context: http, server, location
This directive configures the buffers used for decompression. The first value specifies the number of buffers. The second value specifies the size of each buffer. The default is calculated based on the system memory page size.
For most workloads, the default values work well. Increase buffer count or size only when serving large Brotli-compressed files.
location /large-files/ {
unbrotli on;
unbrotli_buffers 16 8k;
proxy_pass http://storage-backend;
}
Complete Configuration Example
Here is a practical configuration that serves pre-compressed Brotli files from a storage backend while ensuring compatibility with all clients:
upstream storage {
server storage.internal:8080;
}
server {
listen 80;
server_name example.com;
location /assets/ {
# Enable Brotli decompression for legacy clients
unbrotli on;
# Configure decompression buffers
unbrotli_buffers 32 4k;
# Proxy to storage backend that serves .br files
proxy_pass http://storage/;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
This setup pairs well with gzip compression on the fly. You can store content in Brotli format and let NGINX handle client compatibility.
Proxy Buffer Optimization with Brotli
One often-overlooked benefit of the unbrotli module is its impact on proxy buffer efficiency. When upstream servers deliver Brotli-compressed content, the compressed data occupies significantly less memory in NGINX’s proxy buffers compared to uncompressed content.
Consider this scenario with a 100KB uncompressed response:
| Client Support | Data in Proxy Buffers | Memory Used | Decompression |
|---|---|---|---|
| Brotli-capable | ~15KB (compressed) | Low | None needed |
| Legacy client | ~15KB → 100KB | Higher | On-the-fly |
For Brotli-capable clients (the majority of modern browsers), the compressed content passes straight through NGINX without decompression. This means:
- Smaller proxy buffers: Compressed data requires less buffer memory
- Faster upstream reads: Less data to read from upstream connections
- Lower memory pressure: More concurrent connections with the same RAM
- Reduced latency: Less data to buffer means faster time-to-first-byte
You can tune this further with smaller proxy buffer settings when most traffic comes from modern browsers:
location /assets/ {
unbrotli on;
# Smaller buffers work well because Brotli content is compact
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_pass http://storage/;
}
This configuration is ideal for high-traffic sites where memory efficiency matters.
Combining With Other Compression Modules
This filter integrates seamlessly with other compression modules. You can use it alongside Zstd compression or the standard gzip module.
A common pattern is to store assets in Brotli format, use the unbrotli module for legacy clients, and apply gzip compression to the decompressed output. This ensures optimal compression for all visitors:
location /assets/ {
unbrotli on;
gzip on;
gzip_types text/plain text/css application/javascript;
proxy_pass http://storage/;
}
With this configuration, clients receive either Brotli (if supported) or gzip-compressed content (as a fallback). No client receives uncompressed content unless they explicitly refuse compression.
Testing Your Configuration
After configuring the module, verify it works correctly.
First, test the configuration syntax:
nginx -t
Then reload NGINX:
systemctl reload nginx
To verify decompression works, make requests with different Accept-Encoding headers:
# Client WITHOUT Brotli support - should receive decompressed content
curl -s -I http://localhost/assets/file.html | grep -i content-encoding
# Client WITH Brotli support - should receive Brotli-compressed content
curl -s -I -H "Accept-Encoding: br" http://localhost/assets/file.html | grep -i content-encoding
When the unbrotli module is working correctly:
- Requests without
Accept-Encoding: brshould NOT haveContent-Encoding: br - Requests with
Accept-Encoding: brshould haveContent-Encoding: brpreserved
Performance Considerations
The unbrotli module adds CPU overhead for decompression, but only for legacy clients. Consider these factors when deploying.
CPU usage: Brotli decompression is computationally lighter than compression but still requires processing. High-traffic servers may see increased CPU usage when serving many legacy clients. Most modern browsers support Brotli, so this overhead is typically minimal in practice.
Memory allocation: Decompression buffers consume memory. The default configuration uses approximately 128KB per active decompression (32 buffers Ă— 4KB). Adjust unbrotli_buffers based on your memory constraints and concurrent connection count.
Response latency: Decompression adds a small delay before the first byte reaches the client. For most responses, this delay is negligible (microseconds to low milliseconds). Large files may take slightly longer.
When to Use the Unbrotli Module
This filter is most beneficial when:
- Storage I/O is expensive (cloud storage, network-attached storage)
- You serve a mix of modern and legacy clients
- You want to avoid maintaining multiple compressed versions of files
- Your CDN stores pre-compressed content
When to Consider Alternatives
Consider alternatives when:
- CPU resources are severely constrained
- Nearly all clients support Brotli (check your analytics first)
- Responses are very small (compression overhead exceeds savings)
Troubleshooting Common Issues
Module Not Loading
If you see unknown directive "unbrotli", the module is not loaded. Verify the module file exists:
ls /usr/lib64/nginx/modules/ngx_http_unbrotli_filter_module.so
Ensure load_module appears before the events block in nginx.conf. The directive must be at the very top of the file.
Responses Not Being Decompressed
Check that:
- The upstream response includes
Content-Encoding: br - The directive is enabled in the correct location
- You are testing without sending
Accept-Encoding: br
Enable debug logging to trace the filter:
error_log /var/log/nginx/error.log debug;
Look for http unbrotli filter messages in the log.
Decompression Errors
If you see BrotliDecoderDecompressStream() failed in the error log, the upstream is sending invalid or corrupted Brotli data. Verify the upstream content is properly Brotli-compressed:
curl -s http://upstream/file.html | brotli -d > /dev/null
If this command fails, the upstream content is corrupt or not valid Brotli data.
Related NGINX Filter Modules
The unbrotli module belongs to NGINX’s family of response filters. Other useful filter modules include:
- NGINX substitutions filter module: Modify response content on-the-fly with regex replacements
- NGINX Length Hiding Module: Pad responses to prevent BREACH-style attacks
Filter modules process responses in a chain. This decompression filter processes content early in the chain, allowing other filters to work with the decompressed data. NGINX handles the ordering automatically.
Conclusion
The unbrotli module provides an elegant solution for serving pre-compressed Brotli content to all clients regardless of their compression support. By storing content in Brotli format and decompressing on-demand, you achieve significant storage savings while maintaining universal client compatibility.
This filter integrates seamlessly with NGINX’s response chain. It requires minimal configuration to enable transparent decompression. Combined with other compression modules like gzip or Zstd, you can build a comprehensive compression strategy that serves every visitor efficiently.
For source code and updates, visit the ngx_unbrotli GitHub repository.

