For high-traffic Matomo installations, you can squeeze out a small but meaningful performance win by serving precompressed tracking JS (matomo.js / piwik.js) instead of compressing it on every request on the fly.
In our tests this reduced the transfer size of matomo.js by roughly ~10% compared to on-the-fly Brotli, and also saves CPU because the file is compressed once and then reused.
This guide shows how to:
-
Generate
matomo.js.gzandmatomo.js.br(and the same forpiwik.js) -
Keep them in sync after Matomo updates
-
Configure Apache to automatically serve the precompressed versions where supported
1) Precompression script for matomo.js and piwik.js
Create a small script, e.g.:
sudo nano /usr/local/bin/matomo-precompress.sh
Content:
#!/bin/bash
# Path to your Matomo installation (directory where matomo.js / piwik.js lives)
MATOMO_DIR="/var/www/piwik" # <-- adjust to your environment
cd "$MATOMO_DIR" || exit 1
# Files to precompress
FILES=("matomo.js" "piwik.js")
for FILE in "${FILES[@]}"; do
# Skip if the JS file does not exist
if [ ! -f "$FILE" ]; then
continue
fi
# Create / update GZIP
if [ ! -f "$FILE.gz" ] || [ "$FILE" -nt "$FILE.gz" ]; then
echo " -> Creating/updating $FILE.gz"
gzip -k -f -9 "$FILE"
# -k: keep original; -9: max compression
fi
# Create / update Brotli
if [ ! -f "$FILE.br" ] || [ "$FILE" -nt "$FILE.br" ]; then
echo " -> Creating/updating $FILE.br"
brotli -f -q 11 "$FILE"
# ensure .br has the same mtime as the original JS
touch -r "$FILE" "$FILE.br"
fi
done
Make it executable:
sudo chmod +x /usr/local/bin/matomo-precompress.sh
What this does:
-
For each of
matomo.jsandpiwik.js(if present), it:-
generates/updates
*.js.gzusinggzip -9 -
generates/updates
*.js.brusingbrotli -q 11
-
-
It only regenerates the compressed files if the original
.jsis newer, so it’s safe to run frequently.
2) Keep compressed assets up-to-date via cron
Run the script periodically as the webserver user (here: apache, adjust as needed):
sudo -u apache crontab -e
Example cron entry (every 5 minutes):
0,5,10,15,20,25,30,35,40,45,50,55 * * * * /usr/local/bin/matomo-precompress.sh >/dev/null 2>&1
You can also use */30 or @hourly; Matomo’s JS doesn’t change very often.
Whenever Matomo (or a plugin) changes matomo.js, the precompressed files will be updated shortly after.
3) Apache config to serve precompressed matomo.js / piwik.js
In your Apache vhost config, add a <Directory> block for your Matomo root, e.g.:
<Directory "/var/www/piwik">
Options FollowSymLinks
AllowOverride None
Require all granted
# Serve precompressed Matomo/Piwik JS
RewriteEngine On
# JS: prefer .br if client supports Brotli and file exists
RewriteCond %{REQUEST_URI} \.(?:m?js)$ [NC]
RewriteCond %{HTTP:Accept-Encoding} \bbr\b
RewriteCond "%{REQUEST_FILENAME}.br" -f
RewriteRule ^(.+\.(?:m?js))$ $1.br [L]
# JS: fallback to .gz if client supports gzip and file exists
RewriteCond %{REQUEST_URI} \.(?:m?js)$ [NC]
RewriteCond %{HTTP:Accept-Encoding} \bgzip\b
RewriteCond "%{REQUEST_FILENAME}.gz" -f
RewriteRule ^(.+\.(?:m?js))$ $1.gz [L]
# Headers for JS (+ precompressed variants)
<IfModule mod_headers.c>
<FilesMatch "\.(?:m?js)(?:\.(?:br|gz))?$">
Header append Vary "Accept-Encoding"
Header set Content-Type "text/javascript; charset=UTF-8"
Header always set X-Content-Type-Options "nosniff"
</FilesMatch>
</IfModule>
# Avoid double compression for already-compressed files
<IfModule mod_deflate.c>
SetEnvIfNoCase Request_URI "\.(?:br|gz)$" no-gzip=1
</IfModule>
<IfModule mod_brotli.c>
SetEnvIfNoCase Request_URI "\.(?:br|gz)$" no-brotli=1
</IfModule>
# MIME/encoding mappings for .js.br and .js.gz
<IfModule mod_mime.c>
AddType application/javascript .js
AddType application/javascript .js.gz
AddType application/javascript .js.br
AddEncoding gzip .gz
AddEncoding br .br
</IfModule>
</Directory>
Reload Apache:
apachectl -t && systemctl reload httpd
# or the equivalent command on your system
How it behaves:
-
Requests for
matomo.js/piwik.js:-
if browser supports Brotli and
matomo.js.brexists → serve that -
else if browser supports gzip and
matomo.js.gzexists → serve that -
else → serve the plain
matomo.js
-
-
Vary: Accept-EncodingandX-Content-Type-Options: nosniffare set -
mod_deflate/mod_brotliwill not try to recompress already compressed files
From the browser’s view the URL is still /matomo.js, but the response has e.g.:
-
Content-Encoding: br -
Content-Type: text/javascript; charset=UTF-8 -
Vary: Accept-Encoding
4) Note about Matomo file integrity check
One caveat with this approach in Matomo right now:
The file integrity check reports the precompressed files (matomo.js.br, matomo.js.gz, piwik.js.br, piwik.js.gz) as unexpected and suggests deleting them.
It would be useful if Matomo provided a configuration option or whitelist to ignore these specific files in the integrity check so admins can use precompressed assets without a permanent warning.
Hope this helps others running Matomo on higher-traffic setups who want a simple, safe micro-optimization for tracking JS.