Improve Drupal web site performance by compressing files

Compressed web files are smaller and quicker to send. Drupal compresses HTML pages in its page cache, but doesn't compress uncached pages, CSS, and JavaScript. PHP output buffer compression conflicts with Drupal's compression. Instead, configure Apache's mod_gzip or mod_deflate modules.

Introduction

For a small web site served through a cable modem or DSL connection, network bandwidth is limited. Compression can reduce page sizes substantially, giving visitors faster page loads, and enabling the web server to deliver more pages per second. With Drupal's page cache enabled, anonymous visitors are served compressed pages from the page cache. However, Drupal doesn't compress pages for logged-in visitors, and it doesn't compress CSS, JavaScript, XML, text, PostScript, or other text-based files. This article looks at compressing these files too by enabling compression modules for the Apache web server.

Step 1. Enable Drupal's page cache

Enable Drupal's page cache if you haven't already. For anonymous visitors this will reduce HTML page build times by 90%. Pages served from the cache are automatically compressed without any further configuration changes.

Step 2. Don't use PHP's output handler compression

PHP supports optional "obj_gzhandler" and "zlib" output handlers that can automatically compress all data sent from a PHP program. Unfortunately, both of these have problems with Drupal's page cache.

When Drupal sends a compressed page from it's page cache, it correctly marks the page with a "Content-Encoding: gzip" page header. From then on, any software that handles the page should recognize that the page is compressed.  Unfortunately, both of the PHP output handlers ignore this header and mistakenly compress the output a second time. When the doubly-compressed result is shown in a web browser, it's a garbled mess.

Since PHP's output handlers can't be configured to work properly, don't use them. Disable them by editing your "php.ini" file. Set the "output_handler" line to empty, and the "zlib.output_compression" line to "Off". On a new installation of PHP, these are the default values anyway.

output_handler =
zlib.output_compression = Off

Step 3. Enable an Apache compression module

Apache has two commonly-used compression modules: mod_gzip and mod_deflate. Both can be configured to work properly with Drupal.

mod_gzip module

Mod_gzip is free and available for Apache 1.x and 2.x. For Windows PCs, download the DLL file. For Linux PCs and Macs, download the source code and compile it. Installation instructions are included with the downloads. In each case, you'll drag the new module into Apache's module folder.

Next, edit your server's "httpd.conf" file, or your web site's ".htaccess" file to enable and configure the module (replace MODULE below with the path to the installed module).

LoadModule gzip_module MODULE
AddModule mod_gzip.c
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>

LoadModule and AddModule make the module available, and mod_gzip_on turns it on. The mod_gzip_dechunk line directs the module to collect all of Drupal's output before compressing any of it.

The mod_gzip_item_include lines list file types to compress, while mod_gzip_item_exclude lines list what not to. The last line directs mod_gzip to skip compressing files that Drupal has already compressed, such as those from it's page cache.

mod_deflate module

Mod_deflate comes pre-installed with Apache 2.x. To configure it, edit your server's "httpd.conf" file or your site's ".htaccess" file:

<Location />
SetOutputFilter DEFLATE
DeflateCompressionLevel 9
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|gz2|sit|rar)$ no-gzip dont-vary
</Location>

SetOutputFilter enables the module and DeflateCompressionLevel sets the amount of compression from 1 (not much) to 9 (as much as possible). The SetEnvIfNoCase lines block the module from compressing images, executables, and previously compressed files. Everything else gets compressed.

File size results

After adding and configuring a compression module, restart Apache. To confirm that compression is working you'll need to look at the HTTP response header sent from your web server back to your browser. This header isn't normally shown by browsers, so use the free Web Sniffer web site to show the header. You want to see a line like this:

 Content-Encoding: gzip

Google's free Load Time Analyzer 1.5 extension for Firefox provides an easy way to show file sizes before and after enabling compression. Load a web page, then press the extension's "Graph" button to list the files loaded by the page, their sizes, how long each one took to load, and the order in which they were loaded.

The table below lists the files downloaded for the home page of our simple and complex test web sites (see Specifications for Drupal web site testing). The complex site has more modules enabled, so it includes more CSS and image files than does the simple site.

  Simple site (bytes) Complex site (bytes)
  Original Compressed Original Compressed
Simple home page 17,805 4,229    
Complex home page     29,471 6,086
themes/garland/style.css 16,544 3,977 16,544 3,977
files/color/garland-b3c7bd49/style.css     16,437 3,940
modules/system/system.css 6,932 2,003 6,932 2,003
modules/fvestar/theme/fivestar.css     1,356 384
themes/garland/print.css 1,192 499 1,192 499
modules/user/user.css 858 385 858 385
modules/aggregator/aggregator.css     779 303
modules/system/defaults.css 737 415 737 415
modules/node/node.css 717 358 717 358
modules/forum/forum.css     694 318
modules/tagadelic/tagadelic.css     601 234
modules/book/book.css     571 260
modules/devel/devel.css     566 274
modules/poll/poll.css     515 262
modules/cck/content.css     391 391
modules/taxonomy_context/taxonomy_context.css     298 298
TOTAL text 44,785 11,866 78,659 20,387
    (26%)   (26%)
         
themes/garland/logo.png 5,399 5,399    
files/color/garland-b3c7bd49/logo.png     4,274 4,274
themes/garland/images/bg-content-left.png 3,275 3,275    
files/color/garland-b3c7bd49/bg-content-left.png     2,042 2,042
themes/garland/images/bg-content-right.png 3,169 3,169    
files/color/garland-b3c7bd49/bg-content-right.png     1,928 1,928
misc/favicon.ico 1,150 1,150 1,150 1,150
misc/feed.png 764 764 764 764
themes/garland/images/body.png 712 712    
files/color/garland-b3c7bd49/body.png     183 183
themes/garland/images/bg-content.png 485 485    
files/color/garland-b3c7bd49/bg-content.png     274 274
files/images/text.png     662 662
themes/garland/images/menu-leaf.gif 175 175    
files/color/garland-b3c7bd49/menu-leaf.png     274 274
files/color/garland-b3c7bd49/menu-collapsed.png     176 176
themes/garland/images/bg-navigation.png 104 104    
files/color/garland-b3c7bd49/bg-navigation.png     98 98
TOTAL images 15,233 15,233 11,825 11,825
         
TOTAL 60,018 27,099 90,484 32,212
    (45%)   (36%)

Mod_gzip compression only affects text files like HTML, CSS, and JavaScript. Compression reduced text file sizes by 74%. The image files remain unchanged and are a big part of the total data downloaded for the page. Taking image files into account, text file compression reduced the total page size by 55-65%.

Load time results

Smaller files take less time to send. This is particularly important when the web site is served through a limited network connection, such as a cable modem or DSL. To measure compression's impact on load time, we simulated a cable modem's bandwidth limits by using the Charles proxy server to throttle network bandwidth down to the 64 Kbyte/sec typical of a cable modem. Loading a web page through the proxy server logs page sizes and download times.  Compression reduced text file load times by 60-70%. Taking image files into account, compression reduced the total page load time by about 50%.

  Simple site (seconds) Complex site (seconds)
  Original Compressed Original Compressed
Simple home page 4.538 1.251    
Complex home page     7.912 2.866
themes/garland/style.css 3.157 0.640 3.157 0.640
files/color/garland-b3c7bd49/style.css     2.280 0.623
modules/system/system.css 1.492 0.591 1.492 0.591
modules/fvestar/theme/fivestar.css     0.299 0.178
themes/garland/print.css 0.276 0.190 0.276 0.190
modules/user/user.css 0.625 0.182 0.625 0.182
modules/aggregator/aggregator.css     0.225 0.183
modules/system/defaults.css 0.228 0.182 0.228 0.182
modules/node/node.css 0.216 0.173 0.216 0.173
modules/forum/forum.css     0.213 0.175
modules/tagadelic/tagadelic.css     0.204 0.156
modules/book/book.css     0.206 0.160
modules/devel/devel.css     0.199 0.168
modules/poll/poll.css     0.191 0.394
modules/cck/content.css     0.178 0.175
modules/taxonomy_context/taxonomy_context.css     0.433 0.171
TOTAL text 10.532 3.209 18.334 7.207
    (30%)   (39%)
         
themes/garland/logo.png 1.760 1.760    
files/color/garland-b3c7bd49/logo.png     1.062 1.062
themes/garland/images/bg-content-left.png 1.448 1.448    
files/color/garland-b3c7bd49/bg-content-left.png     0.866 0.866
themes/garland/images/bg-content-right.png 0.822 0.822    
files/color/garland-b3c7bd49/bg-content-right.png     0.923 0.923
misc/favicon.ico 0.267 0.267 0.267 0.267
misc/feed.png 0.221 0.221 0.221 0.221
themes/garland/images/body.png 0.220 0.220    
files/color/garland-b3c7bd49/body.png     0.181 0.181
themes/garland/images/bg-content.png 0.214 0.214    
files/color/garland-b3c7bd49/bg-content.png     0.181 0.181
files/images/text.png     0.208 0.208
themes/garland/images/menu-leaf.gif 0.162 0.162    
files/color/garland-b3c7bd49/menu-leaf.png     0.165 0.165
files/color/garland-b3c7bd49/menu-collapsed.png     0.181 0.181
themes/garland/images/bg-navigation.png 0.145 0.145    
files/color/garland-b3c7bd49/bg-navigation.png     0.173 0.173
TOTAL images 5.259 5.259 4.428 4.428
         
TOTAL 15.791 8.468 22.772 11.635
    (54%)   (51%)

Conclusions

Drupal's page cache automatically compresses cached HTML pages for anonymous visitors. Enabling Apache's mod_gzip or mod_deflate modules compresses everything else, including HTML pages for logged-in visitors, as well as CSS, JavaScript, text, and other files. This reduces the total page load time by about 50%.

These tests temporarily disabled Drupal's page cache to measure size and load times of uncompressed and compressed HTML pages. Enabling the page cache shaves 1-3 seconds off the total page load time. While that is certainly good, the page load time is dominated by the time to load CSS and image files, neither of which are affected by Drupal's page cache. Also, the page cache only speeds up page delivery for anonymous visitors. Logged-in visitors will be served custom uncached pages with load times like those above.

What to do next

Measured page load times at cable modem speeds for our simple and complex test sites are still 8 and 11 seconds, respectively. This is way too high. For good usability, we need to be under 1 second.

Nadeau software consulting
Nadeau software consulting