Saturday, 3 May 2025

Adding Brotli compression to Azure Linux App Service (nginx)

No, this post isn't related to Sitecore, but recently I've been working on a site which runs on an Azure App Service box running Linux. These boxes run nginx under the hood (which you can see by SSHing in), which is set up to use gzip for common formats (including your usual HTML / CSS / JS). As we (hopefully) all know, brotli is the preferred option (eg. for Google Page Speed / Lighthouse) due to generally having much better compression for these sorts of file types so I looked into adding it to nginx.

Unfortunately the only relevant search result I found was for an old post I'd made myself, however back when Apache was in use and not nginx.

Now I should preface this by saying: for production environments you really should be using something like Azure Frontdoor to do this, however I wanted something that would work in a non-prod environment without Frontdoor.

Fortunately the README for Brotli is nice and easy to follow in explaining how to build it, and there are plenty of articles out there around adding Brotli to nginx

Just SSH in and run the following script to build 2x *.so files (used in the next script)

#eg. nginx version: nginx/1.26.3
version="$(nginx -v 2>&1 | cut -d '/' -f 2)"
#version=1.26.3 #if you'd rather specify a version

echo "Found nginx version $version"

cd /tmp
  
apt -y install git cmake
  
git clone --recurse-submodules -j8 https://github.com/google/ngx_brotli
#git config --global --add safe.directory /tmp/ngx_brotli/deps/brotli
cd ngx_brotli/deps/brotli
mkdir out && cd out
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed ..
cmake --build . --config Release --target brotlienc
cd ../../../..

wget https://nginx.org/download/nginx-$version.tar.gz
tar -xzvf nginx-$version.tar.gz
cd nginx-$version
  
./configure --with-compat --add-dynamic-module=/tmp/ngx_brotli
make modules
cp objs/ngx_http_brotli_filter_module.so /home/site/nginx
cp objs/ngx_http_brotli_static_module.so /home/site/nginx

Then add the following start.sh file to your app startup

#!/bin/bash

echo "Replacing nginx configuration"
cp /home/site/nginx/nginx.conf /etc/nginx/sites-available/default

echo "Adding brotli module"
cp /home/site/nginx/60-mod-brotli.conf /etc/nginx/modules-enabled/
cp /home/site/nginx/ngx_http_brotli_filter_module.so /usr/share/nginx/modules/
cp /home/site/nginx/ngx_http_brotli_static_module.so /usr/share/nginx/modules/
 
echo "Reloading nginx to apply new configuration"
service nginx reload

Finally, ensure your nginx.conf file (which you use to overwrite above) specifies to use the brotli module before gzip

#   Compression
brotli on;
brotli_static on;
# brotli_comp_level 4;
brotli_types
	text/plain
	text/css
	text/xml
	text/javascript
	text/x-component
	application/xml
	application/xml+rss
	application/javascript
	application/json
	application/atom+xml
	application/vnd.ms-fontobject
	application/x-font-ttf
	application/x-font-opentype
	application/x-font-truetype
	application/x-web-app-manifest+json
	application/xhtml+xml
	application/octet-stream
	font/opentype
	font/truetype
	font/eot
	font/otf
	image/svg+xml
	image/x-icon
	image/vnd.microsoft.icon
	image/bmp;
gzip on;
	#...etc