HTTP Expires Headers

After changing from Wordpress to Pelican, I ran the test on Pingdom Website Speed Test. One of the test findings was, that my webserver does not send an Expires header. I had no idea about this, so I investigated and here are the results.

The Expires header is send with the HTTP response header is sent from the webserver to the browser. It tells the browser, how long the requested resource (e.g. a HTML file, a picture, some javascript, etc.) is valid for caching in the browser cache. A typical Expires header can look like this:

Expires: Fri, 06 Mar 2020 03:57:23 GMT

his header tells the browser, that it does not need to check with the webserver again, if the requested resource has changed until March 6th. After that the browser has to check again to see if there was some modification.

If this header is not present (or if the date is in the past), the next time the browser needs the requested data, it has to contact the webserver again, either to download the resource or at least check its ETag header (similar like a checksum) to ensure there was no server-side modification of the resource.

To learn more about this header, you can have a look at the documentation in the Mozilla Developer Network.

Apache Configuration

If you have a website which is generated server-side, like e.g. when using Wordpress, it totally makes sense that the your browser asks the server for modifications every time a resource is loaded. But when you serve static files, like I'm doing now with the [Pelican] generated HTML files, some CSS and JS files and pictures, we can tell the browser to use cached data for a long time before checking again with the server.

When using the Apache webserver, this can either be configured in the main server configuration file or separately for some directory by using an .htaccess file.

As this website is stored in the /blog subdirectory, I use a .htaccess file with this content:

RewriteEngine On

# This path is prepended to all rewritten URLs.
RewriteBase "/blog/"

# Only rewrite URLs, which do not point to an existing filename or directory
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule "^(\d{4}\/\d{2}/[^.]+)\/?$" "$1.html" [R=301,L]

# Page to show on 404 error. You cannot specify relative paths here!
ErrorDocument 404 /blog/pages/error-404.html

# Add HTTP Expires headers
<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault "access plus 30 seconds"
    ExpiresByType text/html "access plus 1 day"
    ExpiresByType text/css "access plus 7 days"
    ExpiresByType image/jpeg "access plus 7 days"
    ExpiresByType image/png "access plus 7 days"
    ExpiresByType application/javascript "access plus 7 days"
</IfModule>

You can see, that I use the Apache rewrite module to redirect former Wordpress URLs to the corresponding files for Pelican and configure a custom 404 error page. But the interesting part for this post is in the IfModule mod_expires.c section.

Setting expiration headers only works, if the expires module is enabled in Apache. You can do this on the server by running

a2enmod expires

I configure the expiration headers as follows:

  • A default expiration time of 30 seconds.
  • HTML files with 1 day, as the index.html and archives.html update with every new post
  • 7 days for CSS, Javascript and pictures.

The time is measured from access time of the browser.

For configuration details, please check the module documentation for mod_expires on the Apache website.

Does it work?

I can see a huge difference for my website, as the internet connection from my home in Shanghai to the web server in USA is quite slow. Now for many resources, the browser does not start a network request anymore, but directly takes the data from its cache.

You can see this e.g. in Firefox, if you open the developer tools (press F12 to show them) and change to the Networking tab.

When loading the page the first time, you can see in the Transferred column the data sent over the network. If you reload the page with F5 (not Shift+F5, as this disables caching), you can see many lines with cached in this column.

In the developer tools of
Firefox, you can see how many resources are cached now.
In the developer tools of Firefox, you can see how many resources are cached now.
LinkedIn logo mail logo