brotli.sh

brotli.sh tool to auto compress css and js files by specifying path on CentOS based Centmin Mod LEMP web stack servers. Appending clean flag on end of directory path will remove any *.br or *.gz compressed files. I usually place my tools at /root/tools but you can place brotli.sh wherever you want.

Usage

/root/tools/brotli.sh /path/to/parent/directory
/root/tools/brotli.sh /path/to/parent/directory clean
/root/tools/brotli.sh /path/to/parent/directory display

You may want to run a cronjob to automatically run brotli.sh tool on a specific directory every 24hrs etc. Reason why is Nginx gzip_static and brotli_static directives will serve the pre-compressed *.br or *.gz files whenever they are detected. If you update your site's css or js files, then the pre-compressed *.br or *.gz will be out of date and need recompressing. You may want to add a Nginx or web server restart command to cronjob too.

brotli.sh Requirements

  • CentOS with YUM repo for pigz package or you can manually install pigz for your Linux distro before running brotli.sh
  • git for installing Google Brotli binary from their Github repo
  • bc package for file size and compression ratio calculations

brotli.sh Info

First time you run brotli.sh tool, it will detect if Brotli binary is located at /usr/local/bin/brotli and if pigz binary is located at /usr/bin/pigz. If they are not detected, brotli.sh tool will install Brotli binary from official source compile available from Google's Brotli Github repo and install pigz from YUM repo if available.

Default is to enable debug mode with verbose output which includes compression ratio (original size/compressed size). You can set DEBUG=y or DEBUG=n in separate config file brotli-config.ini located in same directory as brotli.sh which will enable or disabledebug mode for more verbose output.

Example running brotli.sh passing the directory path on command line /usr/local/nginx/html/brotlitest2 and GZIP=n and TIMEDSTATS=n and DEBUG=n:

./brotli.sh /usr/local/nginx/html/brotlitest2
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap min.css
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.css
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.js

Example running brotli.sh passing the directory path on command line /usr/local/nginx/html/brotlitest2 and GZIP=n and TIMEDSTATS=n and DEBUG=y:

./brotli.sh /usr/local/nginx/html/brotlitest2
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap min.css.br
[br compression ratio]: 7.50
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
[br compression ratio]: 7.50
[br compress js 37045 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.js --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
[br compression ratio]: 4.24

Example running brotli.sh passing the directory path on command line /usr/local/nginx/html/brotlitest2 and GZIP=n and TIMEDSTATS=y and DEBUG=y:

./brotli.sh /usr/local/nginx/html/brotlitest2
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap min.css.br
[br compress stats]: real: 0.14s user: 0.13s sys: 0.00s cpu: 98% maxmem: 7540 KB cswaits: 2
[br compression ratio]: 7.50
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
[br compress stats]: real: 0.14s user: 0.14s sys: 0.00s cpu: 99% maxmem: 7540 KB cswaits: 1
[br compression ratio]: 7.50
[br compress js 37045 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.js --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
[br compress stats]: real: 0.04s user: 0.03s sys: 0.00s cpu: 97% maxmem: 3316 KB cswaits: 0
[br compression ratio]: 4.24

If GZIP=y variable enabled, you also can use gzip or pigz to create compressed gzip static versions of css and js files along with brotli ones. If only 1 cpu thread is detected, brotli.sh will fall back to gzip binary. If more than 2 cpu threads detected, then use pigz multi-threaded gzip binary. If pigz is used, compression level 11 is used to enable Zopfli based compression for gzip for more compressed files than what gzip level 9 compression can produce.

With GZIP=y and TIMEDSTATS=n and DEBUG=n

/brotli.sh /usr/local/nginx/html/brotlitest2
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap min.css
[gz compress]: /usr/local/nginx/html/brotlitest2/bootstrap min.css
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.css
[gz compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.css
[br compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.js
[gz compress]: /usr/local/nginx/html/brotlitest2/bootstrap.min.js

With GZIP=y and TIMEDSTATS=n and DEBUG=y

./brotli.sh /usr/local/nginx/html/brotlitest2
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap min.css.br
[br compression ratio]: 7.50
[gz compress css 121200 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap min.css
[gz compression ratio]: 6.61
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
[br compression ratio]: 7.50
[gz compress css 121200 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap.min.css
[gz compression ratio]: 6.61
[br compress js 37045 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.js --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
[br compression ratio]: 4.24
[gz compress js 37045 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap.min.js
[gz compression ratio]: 3.90

With GZIP=y and TIMEDSTATS=y and DEBUG=y

./brotli.sh /usr/local/nginx/html/brotlitest2
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap min.css.br
[br compress stats]: real: 0.14s user: 0.14s sys: 0.00s cpu: 99% maxmem: 7540 KB cswaits: 1
[br compression ratio]: 7.50
[gz compress css 121200 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap min.css
[gz compress stats]: real: 0.41s user: 0.39s sys: 0.02s cpu: 100% maxmem: 5124 KB cswaits: 8
[gz compression ratio]: 6.61
[br compress css 121200 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.css --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
[br compress stats]: real: 0.14s user: 0.14s sys: 0.00s cpu: 99% maxmem: 7536 KB cswaits: 1
[br compression ratio]: 7.50
[gz compress css 121200 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap.min.css
[gz compress stats]: real: 0.41s user: 0.38s sys: 0.02s cpu: 99% maxmem: 5124 KB cswaits: 8
[gz compression ratio]: 6.61
[br compress js 37045 bytes]: brotli -q 11 --force /usr/local/nginx/html/brotlitest2/bootstrap.min.js --output=/usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
[br compress stats]: real: 0.04s user: 0.03s sys: 0.00s cpu: 97% maxmem: 3316 KB cswaits: 0
[br compression ratio]: 4.24
[gz compress js 37045 bytes]: pigz -11k -f /usr/local/nginx/html/brotlitest2/bootstrap.min.js
[gz compress stats]: real: 0.09s user: 0.08s sys: 0.00s cpu: 100% maxmem: 3084 KB cswaits: 7
[gz compression ratio]: 3.90

Resulting files

ls -lah /usr/local/nginx/html/brotlitest2
total 236K
drwxr-sr-x  2 root  nginx 4.0K Mar  5 14:44 .
drwxr-sr-x. 5 nginx nginx 4.0K Mar  5 13:33 ..
-rw-r--r--  1 root  nginx 119K Jul 25  2016 bootstrap.min.css
-rw-r--r--  1 nginx nginx  16K Jul 25  2016 bootstrap.min.css.br
-rw-r--r--  1 nginx nginx  18K Jul 25  2016 bootstrap.min.css.gz
-rw-r--r--  1 root  nginx  37K Jul 25  2016 bootstrap.min.js
-rw-r--r--  1 nginx nginx 8.6K Jul 25  2016 bootstrap.min.js.br
-rw-r--r--  1 nginx nginx 9.3K Jul 25  2016 bootstrap.min.js.gz

Example clean appended flag running brotli.sh passing the directory path on command line /usr/local/nginx/html/brotlitest2 withn clean appended

./brotli.sh /usr/local/nginx/html/brotlitest2 clean
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.css.br

cleaned up brotli *.br static css & js files
recursively under /usr/local/nginx/html/brotlitest2

Example clean appended flag with GZIP=y flag enabled

./brotli.sh /usr/local/nginx/html/brotlitest2 clean
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.js.br
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.js.gz
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
rm -rf /usr/local/nginx/html/brotlitest2/bootstrap.min.css.gz

cleaned up brotli *.br static css & js files
recursively under /usr/local/nginx/html/brotlitest2

Added display flag you can append on the end to just find and display all *.br and *.gz compressed css and js files.

./brotli.sh /usr/local/nginx/html/brotlitest2 display

Listing all *.br and *.gz css and js files

/usr/local/nginx/html/brotlitest2/bootstrap min.css.br
/usr/local/nginx/html/brotlitest2/bootstrap.min.css.br
/usr/local/nginx/html/brotlitest2/bootstrap.min.js.gz
/usr/local/nginx/html/brotlitest2/bootstrap min.css.gz
/usr/local/nginx/html/brotlitest2/bootstrap.min.css.gz
/usr/local/nginx/html/brotlitest2/bootstrap.min.js.br

Centmin Mod Nginx + ngx_brotli dynamic module enable

To properly utilise Brotli compression and this brolti.sh tool, your web server needs to be able to serve Brotli Content-Encoding headers to only web browsers that support it. For Nginx there is an ngx_brotli module which can be compiled into Nginx to support such. Centmin Mod latest 123.09beta01 branch's Nginx server has built in support for ngx_brotli module that can optionally be enabled or disabled via persistent config file /etc/centminmod/custom_config.inc set variables below:

NGXDYNAMIC_BROTLI='y'
NGINX_LIBBROTLI='y'

Once variables set, you recompile Centmin Mod Nginx via centmin.sh menu option 4 and will have ngx_brotli support enabled. Centmin Mod Nginx dynamic modules are loaded from /usr/local/nginx/modules directory via an include file /usr/local/nginx/conf/dynamic-modules.conf in /usr/local/nginx/conf/nginx.conf.

centmin.sh menu option 4

--------------------------------------------------------
     Centmin Mod Menu 123.09beta01 centminmod.com     
--------------------------------------------------------
1).  Centmin Install
2).  Add Nginx vhost domain
3).  NSD setup domain name DNS
4).  Nginx Upgrade / Downgrade
5).  PHP Upgrade / Downgrade
6).  XCache Re-install
7).  APC Cache Re-install
8).  XCache Install
9).  APC Cache Install
10). Memcached Server Re-install
11). MariaDB MySQL Upgrade & Management
12). Zend OpCache Install/Re-install
13). Install/Reinstall Redis PHP Extension
14). SELinux disable
15). Install/Reinstall ImagicK PHP Extension
16). Change SSHD Port Number
17). Multi-thread compression: pigz,pbzip2,lbzip2...
18). Suhosin PHP Extension install
19). Install FFMPEG and FFMPEG PHP Extension
20). NSD Install/Re-Install
21). Update - Nginx + PHP-FPM + Siege
22). Add Wordpress Nginx vhost + Cache Plugin
23). Update Centmin Mod Code Base
24). Exit
--------------------------------------------------------
Enter option [ 1 - 24 ] 

In /usr/local/nginx/conf/nginx.conf contains include file to load Nginx dynamic modules

include /usr/local/nginx/conf/dynamic-modules.conf;

And also contains an include file with actual Nginx Brotli settings

include /usr/local/nginx/conf/brotli_inc.conf;

Contents of /usr/local/nginx/conf/dynamic-modules.conf will contain Nginx Brotli's dynamic and static modules

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

Contents of /usr/local/nginx/conf/brotli_inc.conf with ngx_brotli settings

/usr/local/nginx/conf/brotli_inc.conf
brotli on;
brotli_static on;
brotli_min_length 1000;
brotli_buffers 32 8k;
brotli_comp_level 5;
brotli_types text/plain text/css text/xml application/javascript application/x-javascript application/xml application/xml+rss application/ecmascript application/json image/svg+xml;

With Centmin Mod Nginx's Brotli module enabled, this will configure both Brotli on the fly compression as well as support Brotli static file serving if a *.br extension file is detected. To test, you can use curl command with appropate Accept-Encoding directives for gzip and br.

Curl content encoding gzip,br check for Centmin Mod Nginx based server with ngx_brotli enabled. Check for Content-Encoding: br to confirm that Nginx is serving Brotli compressed version of the css or js file.

curl -sI /dev/null -H"Accept-Encoding: gzip,br" https://domain.com/brotlitest2/bootstrap.min.css
HTTP/1.1 200 OK
Date: Sun, 05 Mar 2017 16:39:04 GMT
Content-Type: text/css
Content-Length: 16149
Last-Modified: Mon, 25 Jul 2016 16:08:01 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: "57963961-3f15"
Content-Encoding: br
Server: nginx centminmod
X-Powered-By: centminmod
Expires: Tue, 04 Apr 2017 16:39:04 GMT
Cache-Control: max-age=2592000
Access-Control-Allow-Origin: *
Cache-Control: public, must-revalidate, proxy-revalidate

Curl content encoding gzip check for Centmin Mod Nginx based server with ngx_brotli enabled. Check for Content-Encoding: gzip to confirm that Nginx is serving Gzip compressed version of the css or js file.

curl -sI /dev/null -H"Accept-Encoding: gzip" https://domain.com/brotlitest2/bootstrap.min.css   
HTTP/1.1 200 OK
Date: Sun, 05 Mar 2017 16:40:19 GMT
Content-Type: text/css
Content-Length: 18322
Last-Modified: Mon, 25 Jul 2016 16:08:01 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: "57963961-4792"
Content-Encoding: gzip
Server: nginx centminmod
X-Powered-By: centminmod
Expires: Tue, 04 Apr 2017 16:40:19 GMT
Cache-Control: max-age=2592000
Access-Control-Allow-Origin: *
Cache-Control: public, must-revalidate, proxy-revalidate

Centmin Mod Nginx with ngx_brotli enabled

nginx -V
nginx version: nginx/1.15.0
built by gcc 8.1.0 (GCC) 
built with OpenSSL 1.1.0h 27 Mar 2018
TLS SNI support enabled

configure arguments: --with-ld-opt='-L/usr/local/lib -ljemalloc -Wl,-z,relro -Wl,-rpath,/usr/local/lib' --with-cc-opt='-I/usr/local/include -m64 -march=native -DTCP_FASTOPEN=23 -g -O3 -Wno-error=strict-aliasing -fstack-protector-strong -flto -fuse-ld=gold --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wimplicit-fallthrough=0 -fcode-hoisting -Wno-cast-function-type -Wp,-D_FORTIFY_SOURCE=2 -Wno-deprecated-declarations' --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --with-compat --with-http_stub_status_module --with-http_secure_link_module --add-dynamic-module=../nginx-module-vts --with-libatomic --with-http_gzip_static_module --add-dynamic-module=../ngx_brotli --with-http_sub_module --with-http_addition_module --with-http_image_filter_module=dynamic --with-http_geoip_module --with-stream_geoip_module --with-stream_realip_module --with-stream_ssl_preread_module --with-threads --with-stream=dynamic --with-stream_ssl_module --with-http_realip_module --add-dynamic-module=../ngx-fancyindex-0.4.2 --add-module=../ngx_cache_purge-2.4.2 --add-module=../ngx_devel_kit-0.3.0 --add-dynamic-module=../set-misc-nginx-module-0.32 --add-dynamic-module=../echo-nginx-module-0.61 --add-module=../redis2-nginx-module-0.15 --add-module=../ngx_http_redis-0.3.7 --add-module=../memc-nginx-module-0.18 --add-module=../srcache-nginx-module-0.31 --add-dynamic-module=../headers-more-nginx-module-0.33 --with-pcre=../pcre-8.42 --with-pcre-jit --with-zlib=../zlib-cloudflare-1.3.0 --with-http_ssl_module --with-http_v2_module --with-http_v2_hpack_enc --with-openssl=../openssl-1.1.0h --with-openssl-opt='enable-ec_nistp_64_gcc_128'

Configuration Variables

brotli.sh can specify FILE_MINSIZE variable to define the minimum size of css or js file to be precompressed. Any files greater than FILE_MINSIZE will skip compression routines. Default is set to 1048576 bytes.

FILE_MINSIZE='1048576'

brotli.sh runs are logged to directory defined by variable LOGDIR='/var/log/brotli'. You can override this path in separate config file brotli-config.ini located in same directory as brotli.sh by setting your own LOGDIR variable path

ls -lah /var/log/brotli/
total 24K
drwxr-xr-x   2 root root 4.0K Mar  5 14:03 .
drwxr-xr-x. 13 root root 4.0K Mar  5 13:56 ..
-rw-r--r--   1 root root  574 Mar  5 13:56 brotli.sh_050317-135652.log
-rw-r--r--   1 root root  222 Mar  5 13:57 brotli.sh_050317-135701.log
-rw-r--r--   1 root root  574 Mar  5 14:01 brotli.sh_050317-140151.log
-rw-r--r--   1 root root  222 Mar  5 14:03 brotli.sh_050317-140341.log

Other variables you can override in separate config file brotli-config.ini located in same directory as brotli.sh include the user and group file permissions of the resulting brotli *.br compressed files:

USER=nginx
GROUP=nginx
CHMOD=644
DBEUG=n
TIMEDSTATS=n
BROTLI_LEVEL=11
GZIP=y
GZIP_LEVEL=11
LOGDIR='/var/log/brotli'
FILETYPES=( "*.css" "*.js" )
FILE_MINSIZE='1048576'