Using Brotli Compression in NGINX
Brotli is gaining steam as the compression algorithm du jour for high performance websites. Created back in 2013 by Google to decrease the size of WOFF files, Brotli was standardized in 2016 as part of RFC 7932. The sales pitch for Brotli is better compression than Gzip - with similar CPU usage. Better compression leads to faster performance, but how much better is it?
Using Brotli in NGINX
To find out, we need to enable Brotli compression. Gzip has been available in NGINX for some time. It’s a bit fiddly to ensure you’re compressing all the appropriate mime types, but mostly it’s easy to setup and get going.
Unfortunately, Brotli is not part of the default NGINX package at this time, so you have to load it as a dynamic or static module. The Brotli NGINX module is currently developed and maintained by the folks at Google.
The Easy (but Expensive) Way
NGINX Plus has official support for the Brotli module. They’ve done all the hard work of packaging the module in to the NGINX Plus repository for you. It’s just an apt-get install
away! That is - if you’re a paying customer.
The Hard Way
Since we’re cheap, we don’t have access to the NGINX Plus repository. We only have the open source version of NGINX, which means we need to load the module by hand. The process is roughly this:
- Install a load of pre-reqs
- Compile the module for your system (groan)
- Configure NGINX to use the newly compiled module
Installing Dependencies
Even though you’ve likely installed NGINX via some kind of package manager (apt, yum, whatever) you’re still going to need the source for the version you’ve installed. Even worse, you’re going to need all the dependencies that the source was originally compiled against. From the horse’s mouth:
Dynamic modules must be compiled against the same version of NGINX they are loaded into.
And from the Google Brotli module installation docs:
You will need to use exactly the same ./configure arguments as your Nginx configuration and append –with-compat –add-dynamic-module=/path/to/ngx_brotli to the end, otherwise you will get a “module is not binary compatible” error on startup. You can run nginx -V to get the configuration arguments for your Nginx installation.
So that all sounds complicated and terrible, but fortunately it’s not too bad if you are OK with some blind faith and a little copy/pasting. In all the following examples, we’re using Ubuntu 20.04.
Installing all the Pre-Reqs
apt-get install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev \
libssl-dev libgd-dev libxml2 libxml2-dev uuid-dev libxslt-dev
Once you’ve got those, you’re going to need the NGINX source and the Brotli module source.
nginx -v
# nginx version: nginx/1.18.0 (Ubuntu)
Ok, so we need 1.18.0 since that’s what’s installed on our system. We also need to clone the Brotli module from Github. There are Git submodules in play, so make sure when you clone the Brotli module repository you use the --recursive
flag!
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -xzvf nginx-1.18.0.tar.gz
# Ensure you use the --recursive flag
git clone https://github.com/google/ngx_brotli.git --recursive
Getting the Configuration Arguments Right
So remember all that talk about binary compatability? When NGINX was originally compiled for your system, it was compiled with a whole slew of configure arguments. When we compile the module, we need to make sure they are exactly the same, plus we need to add a few more! To see the original configure arguments we can use the -V
flag (note the capital letter this time)
Copy that huge list of configure arguments and paste it somewhere for safekeeping. Now comes the fun part.
Compile Time, Baby
cd nginx-1.18.0
# Paste in the configure arguments and then add the second line
# If your system is different, ./configure may spit out
# additional dependencies you need to install
./configure [ALL THE CONFIGURE ARGUMENTS] \
--with-compat --add-dynamic-module=../ngx_brotli
# If ./configure finishes successfully, you can make the module!
make modules
The compiled modules will be emitted to the obj/
folder within the source directory once compilation is successful. We need to move the .so
files to a place NGINX can find them.
cp objs/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules
cp objs/ngx_http_brotli_static_module.so /usr/lib/nginx/modules
Huzzah! Now we’re ready to configure NGINX. Having fun yet?
Configuring NGINX
This is the easy part. In nginx.conf
you need to load the module. This needs to be the very first thing in the file.
# for compressing responses on-the-fly
load_module modules/ngx_http_brotli_filter_module.so;
# for serving pre-compressed files
load_module modules/ngx_http_brotli_static_module.so;
And then we need to turn it on! You can enable Brotli in specific location blocks, or at the server level.
brotli on;
brotli_types
application/javascript
application/json
image/svg+xml
text/css
text/plain;
# add more types as needed
And finally, we need to test our configuration to make sure we did everything right, and then reload NGINX
nginx -t && nginx -s reload
Brotli in Action
Let’s see how well it works! The JavaScript application bundle for Request Metrics is 600KB uncompressed:
And if we Gzip it like we usually do, it’s 209KB , a 65% reduction.
But what happens when we enable Brotli instead?
The file is compressed to 161KB , a 48KB (23%) improvement over Gzip!
Is it Worth It?
There’s only one way to find out. Use a tool like Request Metrics to capture real-world performance metrics from your users. Switch to using Brotli and measure the results! With our new custom tagging you could even run an A/B test with some machines compressing with Gzip and others using Brotli. It takes just seconds to set up the agent and start getting real data.