How to Fix Apache mod_deflate Not Compressing CSS/JS Files with Query Parameters


2 views

When using Apache's mod_deflate for compression, you might notice that CSS and JavaScript files containing query parameters (e.g., aggregator.css?version=1.2 or jquery.js?cache=clear) aren't being gzipped, while their parameter-less counterparts are properly compressed. This occurs because of how FilesMatch pattern matching works with URLs containing query strings.

The standard configuration:

<IfModule mod_deflate.c>
    <FilesMatch "\.(css|js|x?html?|php)$">
        SetOutputFilter DEFLATE
    </FilesMatch>
</IfModule>

only matches against the filename portion of the URI, not the query string. The ? and everything after it in a URL is considered part of the query string, which means files with parameters fall outside the matching pattern.

Solution 1: Using LocationMatch

Replace FilesMatch with LocationMatch which examines the entire request URI:

<IfModule mod_deflate.c>
    <LocationMatch "\.(css|js|x?html?|php)(\?.*)?$">
        SetOutputFilter DEFLATE
    </LocationMatch>
</IfModule>

The pattern (\?.*)?$ makes the query string optional while still capturing the entire URI.

Solution 2: Separate Rules for Parameterized Files

For more precise control, create separate matching rules:

<IfModule mod_deflate.c>
    # Standard files
    <FilesMatch "\.(css|js|x?html?|php)$">
        SetOutputFilter DEFLATE
    </FilesMatch>
    
    # Files with parameters
    <LocationMatch "\.(css|js)(\?.*)?$">
        SetOutputFilter DEFLATE
    </LocationMatch>
</IfModule>
  • Always test changes using curl: curl -I -H "Accept-Encoding: gzip" "http://yoursite.com/file.js?param=1"
  • Consider adding these headers for better caching:
    Header append Vary: Accept-Encoding
    Header append Vary: User-Agent
  • For dynamic files (like PHP), ensure compression isn't being handled at both Apache and application levels

While enabling compression for parameterized files improves performance, be aware that:

  • Each unique URL (including different parameters) creates a separate cache entry
  • Highly dynamic parameters (like session IDs) should be excluded from compression rules
  • Monitor memory usage when enabling compression for many unique URLs

Enable mod_deflate logging by adding:

DeflateFilterNote Input instream
DeflateFilterNote Output outstream
DeflateFilterNote Ratio ratio
LogFormat "\"%r\" %{outstream}n/%{instream}n (%{ratio}n%%)" deflate
CustomLog logs/deflate_log deflate

This helps identify which files are being compressed and their compression ratios.


When examining YSlow or Chrome DevTools, you might notice certain CSS/JS resources aren't being gzipped despite having mod_deflate properly configured. This typically occurs with URLs containing query parameters like:

https://example.com/aggregator.css?ver=1.0
https://example.com/misc/jquery.js?cache=12345

The default pattern in most Apache configurations doesn't account for query strings. While your rule matches the file extension, Apache's internal matching logic may not handle the full URL pattern correctly when query parameters exist.

Modify your .htaccess configuration with this improved pattern:


    
        SetOutputFilter DEFLATE
    

The key improvement is (\?.*)?$ which makes the query string optional in the pattern matching.

Using cURL:

curl -I -H "Accept-Encoding: gzip,deflate" "https://yoursite.com/script.js?param=1"

Look for Content-Encoding: gzip in the response headers.

Browser DevTools:
Check the Network tab and examine the Content-Encoding header for affected resources.

For maximum compatibility, you can force compression for all text-based content regardless of extension:


    AddOutputFilterByType DEFLATE text/html text/plain text/xml 
    AddOutputFilterByType DEFLATE text/css text/javascript application/javascript
    AddOutputFilterByType DEFLATE application/json application/xml

When dealing with parameterized assets, ensure your caching headers are properly configured to avoid duplicate compression:


    FileETag None
    Header unset ETag
    Header set Cache-Control "max-age=2592000, public"