Why Compression Still Matters in 2025
There is a persistent myth in web development that compression is a solved problem, something you tick once and forget. On a well-resourced VPS running Nginx, maybe. On cPanel shared hosting serving a South African audience over mobile connections, absolutely not.
According to the HTTP Archive 2024 data, text assets (HTML, CSS, JavaScript) account for roughly 30% of median page weight before any compression is applied. That is the baseline you are starting from. Brotli typically achieves 15 to 20% better compression ratios than gzip on those same text assets, which is not a rounding error. On a 400KB JavaScript bundle, that difference is 60 to 80KB. Multiply that by every page load, every visitor on a 10-15 Mbps LTE connection in Joburg or Cape Town paying per-gigabyte data, and it matters.
PageSpeed Insights will flag missing compression as a medium-severity issue. Google's CWV scores weight LCP heavily, and shaving 80KB off your critical path JS directly affects that number. If you are running any kind of commercial site in South Africa and your hosting is on cPanel shared (which is most affordable ZA hosting), this is the lowest-effort, highest-return config change you can make.
This post covers exactly how to get both Brotli and gzip working on cPanel shared hosting via .htaccess, what to test, and where things break.
How cPanel Shared Hosting Exposes mod_deflate and mod_brotli
cPanel shared hosting almost always runs Apache. The two relevant modules are mod_deflate (gzip, been around since the early 2000s) and mod_brotli (available since Apache 2.4.26). Whether they are loaded depends entirely on your host's Apache build and configuration.
The important thing to understand is that you do not control the Apache server config directly. You control .htaccess. The IfModule directive is what saves you here: it wraps your directives so that if the module is not loaded, Apache silently ignores the block instead of throwing a 500.
Most South African cPanel hosts (Hetzner, Absolute Hosting, Host Africa, xneelo) have mod_deflate enabled by default. mod_brotli is less consistent. Some run it, some don't. The approach below handles both cases gracefully: Brotli serves where available, gzip serves as fallback, and the config does not break anything if neither module is present.
One thing cPanel does do for you: it exposes per-directory .htaccess overrides, which means you can apply this config in your document root without needing root SSH access or WHM intervention. That is the entire point of this guide.
Browsers advertise Brotli support via the Accept-Encoding: br request header. According to MDN, all major browsers have supported the br encoding since 2017, and caniuse.com puts global browser support at 97.5% as of 2025. You are not risking anything by enabling it. If the browser does not advertise br, Apache serves gzip. If neither, it serves uncompressed. That is the fallback chain Apache handles for you.
The Full .htaccess Block That Actually Works
Before you paste anything: if you already have a .htaccess in your document root (almost certain if you are on WordPress or any framework), do not replace it. Merge these blocks in. Find any existing compression directives and remove them first to avoid conflicts, then add the blocks below.
Here is the complete working config:
# Brotli compression (Apache 2.4.26+, mod_brotli required)
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml image/svg+xml
BrotliCompressionQuality 5
</IfModule>
# Gzip compression fallback (mod_deflate)
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml image/svg+xml
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
</IfModule>
# Skip already-compressed binary formats
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|mp3|zip|gz|br|pdf)$ no-compress
<IfModule mod_deflate.c>
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|mp3|zip|gz|br|pdf)$ no-gzip
</IfModule>
# Correct caching behaviour for compressed responses
<IfModule mod_headers.c>
Header append Vary Accept-Encoding
</IfModule>
Let me walk through what each part is doing and why it matters.
BrotliCompressionQuality 5
The Apache mod_brotli documentation puts the quality range at 0 to 11. Quality 11 gives you the smallest output but burns significant CPU on each compression operation. On shared hosting where you are sharing CPU with dozens of other accounts, quality 11 is going to hurt response times under any meaningful load. Quality 5 is the documented sweet spot for shared environments: roughly 80% of the size savings of quality 11, at a fraction of the CPU cost. You can go up to 6 or 7 if your host is generous with CPU limits and your traffic is low, but 5 is the safe default.
The BrowserMatch Directives in mod_deflate
Those three BrowserMatch lines look like archaeology, and they are. They handle ancient Netscape 4.x and early IE bugs where gzip responses caused rendering failures. No real-world traffic hits those patterns in 2025. They are in the Apache mod_deflate documentation as standard boilerplate and they cost nothing to include. Leave them in.
Vary: Accept-Encoding
This is the one people skip and then wonder why their CDN or reverse proxy is serving compressed content to clients that cannot decode it. The Vary: Accept-Encoding response header tells caches (Cloudflare, Varnish, the browser's own disk cache) that the response content varies based on what the client advertises in Accept-Encoding. Without it, a cache might store the Brotli version of a page and serve it to a client that only asked for gzip. With it, caches key their stored responses on the encoding, and serve the right version to each client.
mod_deflate should add this header automatically on modern Apache versions. mod_brotli does not always. The explicit Header append Vary Accept-Encoding line is belt and braces. It does no harm if the header is already present (append means it adds a second value, which is valid) and it saves you from debugging a subtle caching bug later.
Skipping Already-Compressed Formats
This is the part most blog posts get wrong or skip entirely. JPEG, PNG, WebP, AVIF, MP4, MP3, ZIP, and PDF files are already compressed internally. Trying to gzip or Brotli-compress them wastes CPU, often slightly increases their size, and can corrupt certain binary formats. The SetEnvIfNoCase directives match on the request URI extension and set an environment variable that tells mod_deflate (and by extension mod_brotli) to leave those files alone.
Note that no-compress is a generic convention and no-gzip is the specific env variable mod_deflate checks. Both are included because some Apache versions behave differently. The no-gzip one inside the IfModule block covers the mod_deflate path explicitly.
Testing Your Config
Do not assume it worked. Verify it. Two methods: curl from the terminal, and Chrome DevTools for the browser view.
curl Verification
curl -H "Accept-Encoding: br,gzip,deflate" -I https://yourdomain.co.za/
The -I flag fetches headers only. The -H flag injects the Accept-Encoding header the same way a browser would. Replace yourdomain.co.za with your actual domain.
In the response, look for this header:
Content-Encoding: br
That confirms Brotli is active. If you see Content-Encoding: gzip, mod_brotli is not loaded on your host but gzip fallback is working. If you see neither, compression is not active and you need to check whether the modules are available at all (contact your host or check cPanel's MultiPHP Manager or Apache configuration screens).
You should also see:
Vary: Accept-Encoding
If that is missing, the mod_headers block is not active. Some hosts disable mod_headers in shared environments. If that is your situation, you can remove the Header append block from your config and rely on whatever the module sets automatically. It is not going to break compression, it just affects cache correctness.
Chrome DevTools Verification
Open Chrome, navigate to your site, open DevTools (F12), go to the Network tab. Reload the page. Click on the HTML document request (the first one). Under Response Headers, look for content-encoding: br or content-encoding: gzip.
Chrome DevTools also shows you transferred size vs resource size in the Network tab summary at the bottom. If compression is working you will see the transferred size significantly smaller than the resource size on text assets. A 40KB HTML file might transfer as 8KB. That gap is compression at work.
For PageSpeed Insights validation: run your URL through pagespeed.web.dev before and after. The "Enable text compression" audit should move from flagged to passed. If you were failing that audit before, you will see a measurable improvement in your Performance score, typically 2 to 5 points depending on your page's asset profile.
Common Gotchas
Double Compression
If your host has already enabled gzip at the server level (check cPanel's "Optimize Website" feature under "Software"), and you add your own mod_deflate directives in .htaccess, you can end up with responses that get compressed twice. The result is a file that the browser cannot decode, which presents as a blank page or a download prompt for what should be an HTML page.
Before applying this config, go into cPanel and check the "Optimize Website" setting. If it is enabled, disable it first. The .htaccess approach gives you more control (correct MIME types, skipping binary formats) than the cPanel slider does. You do not want both active simultaneously.
The .svgz Trap
SVG files compressed with gzip carry the .svgz extension. These are already compressed at the file level. If you add image/svg+xml to your compression directives (which you should, for regular .svg files) but you are also serving .svgz files, Apache will try to compress an already-compressed binary and corrupt it.
Fix: add svgz to your exclusion pattern.
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|mp3|zip|gz|br|pdf|svgz)$ no-compress
<IfModule mod_deflate.c>
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|mp3|zip|gz|br|pdf|svgz)$ no-gzip
</IfModule>
If you are not serving any .svgz files (most sites do not), the original pattern is fine. But if you pulled SVG icons from an older icon library or received assets from a designer who exported in that format, check your asset inventory.
What We Gave Up: Brotli on HTTP
Brotli only works over HTTPS. That is a protocol-level constraint, not an Apache limitation. If any part of your site serves over plain HTTP, requests to those URLs will not receive Brotli responses even with this config in place. They will fall through to gzip, which is fine functionally but means you should ensure you are running SSL everywhere. cPanel's AutoSSL handles this for free via Let's Encrypt. If you are not already forcing HTTPS, that is a prerequisite.
WordPress and Plugin Conflicts
Some WordPress performance plugins (W3 Total Cache, WP Rocket, LiteSpeed Cache when misconfigured) manage their own output compression. If you are running one of these and you add .htaccess compression directives, you may hit the same double-compression problem. Check your plugin's settings for "GZIP compression" or "Brotli compression" toggles. If the plugin handles it, let it, and do not add the Apache-level directives. If the plugin does not handle Brotli (W3 Total Cache older versions), you may want to disable the plugin's gzip option and use this .htaccess approach instead, since it adds Brotli support the plugin lacks.
mod_brotli Not Available
If your host has not compiled mod_brotli into Apache, the IfModule mod_brotli.c block is ignored silently and gzip runs via mod_deflate. That is the correct behaviour. You are not losing anything; you are just not gaining the extra 15 to 20% compression ratio improvement Brotli provides. To find out whether your host has mod_brotli, ask their support team directly or check a PHP info page (phpinfo()) for the loaded Apache modules section. On Absolute Hosting and xneelo, I have seen it present on their newer shared infrastructure. Older shared plans on some hosts are still on Apache 2.4.x builds that predate mod_brotli or that were built without it.
.htaccess Inheritance and Subdirectories
If you have .htaccess files in subdirectories (common in WordPress multisite or multi-app setups), the compression directives in the root .htaccess apply to everything under that directory by default. You generally want that. But if a subdirectory has its own .htaccess that contains conflicting compression directives, the subdirectory file can override or conflict with the root. Audit your directory structure before assuming root-level config applies everywhere.
The Result
This is a one-time config change that stays in version control, deploys with your codebase, and keeps working without maintenance. The investment is about 15 minutes including testing. The payoff is measurable: smaller asset payloads, faster LCP on mobile, and a passing grade on the PageSpeed compression audit.
If you run into issues with your specific host's Apache build, or you are on LiteSpeed (which handles compression via its own directives and ignores mod_brotli blocks entirely), the approach differs. And if you are managing multiple cPanel servers and want to audit compression settings across the fleet without logging into each one manually, that is a different conversation.
We help South African businesses and developers tune their hosting stacks without the overhead of a full agency. If you are dealing with slow WordPress sites, misconfigured caching, or compression problems that go deeper than .htaccess, drop us a line below and we will take a look.
