Compress and Cache Static Resources

Last modified by Eleni Cojocariu on 2026/10/21 15:51

Steps

Skins, stylesheets, scripts and WebJars are the bulk of what a page downloads and they change rarely, so compressing them and letting the browser keep its copy saves more than most server-side tuning does: see Performance. Neither XWiki nor its servlet container compresses a response by default, so this configuration belongs to the Apache HTTP Server in front of it.

  1. Enable the three modules the configuration needs: deflate compresses, expires writes the expiry headers, and filter is what selects the responses to compress by their content type. On Debian and Ubuntu, as root:
    a2enmod deflate expires filter

    On a distribution without a2enmod, load them in the server configuration instead. The module paths below are the Debian ones; elsewhere they are usually relative to the server root, as in modules/mod_deflate.so.

    LoadModule deflate_module /usr/lib/apache2/modules/mod_deflate.so
    LoadModule expires_module /usr/lib/apache2/modules/mod_expires.so
    LoadModule filter_module  /usr/lib/apache2/modules/mod_filter.so
  2. Compress the responses that are text. Put the block in the VirtualHost that serves the wiki, next to the proxy directives described in Apache Server Key Configurations. The list is an allow list, so images, fonts and archives are left alone without a single exclusion rule.
    <Location /xwiki>
      AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
      AddOutputFilterByType DEFLATE text/javascript application/javascript application/json
      AddOutputFilterByType DEFLATE application/xml image/svg+xml
    </Location>

    An SVG is XML and compresses like any other text, which is why image/svg+xml is in the list.
    See AddOutputFilterByType and mod_deflate.

  3. Add expiry headers to the two paths XWiki does not already cache. XWiki writes them itself on the URLs it serves through its own servlets: one year on /xwiki/webjars/, whose URLs carry the version of the artifact, and thirty days on /xwiki/bin/skin/, /xwiki/bin/ssx/ and /xwiki/bin/jsx/, so those paths need nothing here. /xwiki/resources/ and /xwiki/skins/ are served by the container's default servlet, which sends no expiry at all.
    <Location /xwiki/resources>
      ExpiresActive On
      ExpiresDefault "access plus 1 day"
    </Location>
    
    <Location /xwiki/skins>
      ExpiresActive On
      ExpiresDefault "access plus 1 day"
    </Location>

    One day is a safe figure: these files have no version in their URL, so a reader keeps a stale copy for as long as the expiry lasts after an upgrade or a skin change. See ExpiresDefault.

  4. Check the configuration and reload the server. configtest parses the files without restarting anything, which is worth doing before the reload rather than after it.
    apache2ctl configtest
    systemctl reload apache2
  5. Request a static resource and read the response headers. content-encoding: gzip is the compression, vary: Accept-Encoding is added by mod_deflate so that caches keep the compressed and the uncompressed answer apart, and cache-control: max-age=86400 is the one day configured above.
    curl -I -H 'Accept-Encoding: gzip' https://wiki.example.com/xwiki/resources/js/xwiki/xwiki.js
    HTTP/1.1 200 OK
    Date: Wed, 21 Oct 2026 10:10:32 GMT
    Server: Apache/2.4.58 (Ubuntu)
    Last-Modified: Tue, 29 Sep 2026 09:02:46 GMT
    Content-Type: text/javascript;charset=utf-8
    Vary: Accept-Encoding
    Content-Encoding: gzip
    Cache-Control: max-age=86400
    Expires: Thu, 22 Oct 2026 10:10:32 GMT

    Without the configuration the same request answers with a Content-Length of the full file and none of the three headers above.

FAQ

Why an allow list of content types instead of compressing everything?

SetOutputFilter DEFLATE compresses every response from a location, including the images, fonts and archives that are already compressed, and gzip makes those slightly larger while costing CPU on every request. Compressing by content type is the same result without the list of exclusions that approach needs.

Where does the deflate configuration live on Debian?

Enabling the module symlinks /etc/apache2/mods-available/deflate.conf to /etc/apache2/mods-enabled/deflate.conf, and that file already compresses text/html, text/plain, text/xml, text/css, text/javascript, application/javascript, application/ecmascript, application/rss+xml, application/wasm and application/xml server-wide. The block in step 2 adds application/json and image/svg+xml and scopes the whole set to the wiki; a server that hosts only the wiki can edit deflate.conf instead.

Does the servlet container not compress?

It can: Tomcat has a compression attribute on its connector, and so do most of the others. But the HTTP server in front of the wiki is where it costs least: it compresses the files it serves itself as well as the ones it proxies, and it does so without taking a request thread from XWiki.

How is this configured on NginX?

With gzip on and a gzip_types list for the compression, and an expires directive for the caching. NginX Server Key Configurations documents the location /xwiki block these go in.

gzip on;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml image/svg+xml;

# Nested inside location /xwiki, so that it inherits the proxy_set_header directives.
location ~ ^/xwiki/(resources|skins)/ { expires 1d; proxy_pass http://localhost:8080; }

Related

Get Connected