Table of Contents

Ubuntu - Nginx - Cache static files on Nginx

Configure nginx to set the Expires HTTP header and the max-age directive of the Cache-Control HTTP header of static files (such as images, CSS and Javascript files) to a date in the future so that these files will be cached by your visitors' browsers.

This saves bandwidth and makes your web site appear faster (if a user visits your site for a second time, static files will be fetched from the browser cache).


Configuring nginx

The Expires HTTP header can be set with the help of the expires directive which can be placed in inside http {}, server {}, location {}, or an if statement inside a location {} block.

Usually you will use it in a location block for your static files, e.g. as follows:

location ~*  \.(jpg|jpeg|png|gif|ico|css|js)$ {
   expires 365d;
}

In the above example, all .jpg, .jpeg, .png, .gif, .ico, .css, and .js files get an Expires header with a date 365 days in the future from the browser access time.

Therefore, you should make sure that the location {} block really only contain static files that can be cached by browsers.


Reload nginx

/etc/init.d/nginx reload

You can use the following time settings with the expires directive:


You can use the following time units:

Examples: 1h30m for one hour thirty minutes, 1y6M for one year and six months.

Also note that if you use a far future Expires header you have to change the component's filename whenever the component changes.

Therefore it's a good idea to version your files.

For example, if you have a file javascript.js and want to modify it, you should add a version number to the file name of the modified file (e.g. javascript-1.1.js) so that browsers have to download it.

If you don't change the file name, browsers will load the (old) file from their cache.

Instead of basing the Expires header on the access time of the browser (e.g. expires 10d;), you can also base it on the modification date of a file (please note that this works only for real files that are stored on the hard drive!) by using the modified keyword which precedes the time:

expires modified 10d;

Configuring Cache-Control and Expires Headers

There are two caching control response headers: Cache-Control and Expires.

Cache-Control is the newer version, which has more options than Expires and is generally more useful if you want finer control over your caching behavior.

If these headers are set, they can tell the browser that the requested file can be kept locally for a certain amount of time (including forever) without requesting it again.

If the headers are not set, browsers will always request the file from the server, expecting either 200 OK or 304 Not Modified responses.

We can use the header module to set these HTTP headers.

The header module is a core Nginx module, which means it doesn't need to be installed separately to be used.

To add the header module, edit the default Nginx configuration file.

sudo vi /etc/nginx/sites-available/default

Find the server configuration block, which looks like this:

/etc/nginx/sites-available/default
. . .
# Default server configuration
#
 
server {
    listen 80 default_server;
    listen [::]:80 default_server;
 
. . .

Add the following two new sections here: one before the server block, to define how long to cache different file types, and one inside it, to set the caching headers appropriately.

/etc/nginx/sites-available/default
. . .
# Default server configuration
#
 
# Expires map
map $sent_http_content_type $expires {
    default                    off;
    text/html                  epoch;
    text/css                   max;
    application/javascript     max;
    ~image/                    max;
}
 
server {
    listen 80 default_server;
    listen [::]:80 default_server;
 
    expires $expires;
. . .

The section before the server block is a new map block which defines the mapping between the file type and how long that kind of file should be cached.

We're using several different settings in this map:

Inside the server block, the expires directive (a part of the headers module) sets the caching control headers. It uses the value from the $expires variable set in the map. This way, the resulting headers will be different depending on the file type.

Save and close the file to exit.

To enable the new configuration, restart Nginx.

sudo systemctl restart nginx

Next, let's make sure our new configuration works.


Testing

To test if your configuration works, you can use the Network analysis function of the Developer tools in the Firefox Browser and access a static file through Firefox (e.g. an image).

In the Header output, you should now see an Expires header and a Cache-Control header with a max-age directive (max-age contains a value in seconds, for example 31536000 is one year in the future):


Creating Test Files

In this step, we will create several test files in the default Nginx directory.

We'll use these files later to check Nginx's default behavior and then to test that browser caching is working.

To make a decision about what kind of file is served over the network, Nginx does not analyze the file contents; that would be prohibitively slow.

Instead, it just looks up the file extension to determine the file's MIME type, which denotes the purpose of the file.

Because of this behavior, the content of our test files is irrelevant.

By naming the files appropriately, we can trick Nginx into thinking that, for example, one entirely empty file is an image and another is a stylesheet.

Create a file named test.html in the default Nginx directory using truncate. This extension denotes that it's an HTML page.

sudo truncate -s 1k /var/www/html/test.html

Let's create a few more test files in the same manner: one jpg image file, one css stylesheet, and one js JavaScript file.

sudo truncate -s 1k /var/www/html/test.jpg
sudo truncate -s 1k /var/www/html/test.css
sudo truncate -s 1k /var/www/html/test.js

The next step is to check how Nginx behaves with respect to sending caching control headers on a fresh installation with the files we have just created.


Checking the Default Behavior

By default, all files will have the same default caching behavior.

To explore this, we'll use the HTML file we created earlier, but you can run these tests with any of the example files.

So, let's check if test.html is served with any information regarding how long the browser should cache the response.

The following command requests a file from our local Nginx server and shows the response headers.

curl -I http://localhost/test.html

You should see several HTTP response headers:

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 10 Sep 2016 13:12:26 GMT
Content-Type: text/html
Content-Length: 1024
Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT
Connection: keep-alive
ETag: "57d40685-400"
Accept-Ranges: bytes

In the second to last line you can see the ETag header, which contains a unique identifier for this particular revision of the requested file.

If you execute the previous curl command repeatedly, you will see the exact same ETag value.

When using a web browser, the ETag value is stored and sent back to the server with the If-None-Match request header when the browser wants to request the same file again — for example, when refreshing the page.

We can simulate this on the command line with the following command. Make sure you change the ETag value in this command to match the ETag value in your previous output.

curl -I -H 'If-None-Match: "57d40685-400"' http://localhost/test.html

The response will now be different:

HTTP/1.1 304 Not Modified
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 10 Sep 2016 13:20:31 GMT
Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT
Connection: keep-alive
ETag: "57d40685-400"

This time, Nginx will respond with 304 Not Modified. It won't send the file over the network again; instead, it will tell the browser that it can reuse the file it already has downloaded locally.

This is useful because it reduces network traffic, but it's not quite good enough for achieving good caching performance.

The problem with ETag is that browser always sends a request to the server asking if it can reuse its cached file. Even though the server responds with a 304 instead of sending the file again, it still takes time to make the request and receive the response.

In the next step, we will use the headers module to append caching control information. This will make the browser to cache some files locally without explicitly asking the server if its fine to do so.


Testing Browser Caching

Execute the same request as before for the test HTML file.

curl -I http://localhost/test.html

This time the response will be different. You should see two additional HTTP response headers:

HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 10 Sep 2016 13:48:53 GMT
Content-Type: text/html
Content-Length: 1024
Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT
Connection: keep-alive
ETag: "57d40685-400"
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Cache-Control: no-cache
Accept-Ranges: bytes

The Expires header shows a date in the past and Cache-Control is set with no-cache, which tells the browser to always ask the server if there is a newer version of the file (using the ETag header, like before).

You'll see a difference response with the test image file.

curl -I http://localhost/test.jpg

result:

Nginx response headers
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 10 Sep 2016 13:50:41 GMT
Content-Type: image/jpeg
Content-Length: 1024
Last-Modified: Sat, 10 Sep 2016 13:11:36 GMT
Connection: keep-alive
ETag: "57d40688-400"
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Accept-Ranges: bytes

In this case, Expires shows the date in the distant future, and Cache-Control contains max-age information, which tells the browser how long it can cache the file in seconds.

This tells the browser to cache the downloaded image for as long as it can, so any subsequent appearances of this image will use local cache and not send a request to the server at all.

The result should be similar for both test.js and test.css, as both JavaScript and stylesheet files are set with caching headers too.

This means the cache control headers have been configured properly and your website will benefit from the performance gain and less server requests due to browser caching.

You should customize the caching settings based on the content for your website, but the defaults in this article are a reasonable place to start.


Conclusion

The headers module can be used to add any arbitrary headers to the response, but properly setting caching control headers is one of its most useful applications.

It increases performance for the website users, especially on networks with higher latency, like mobile carrier networks.

It can also lead to better results on search engines that factor speed tests into their results. Setting browser caching headers is one of the major recommendations from Google's PageSpeed testing tools.

More detailed information about the headers module can be found in Nginx's official headers module documentation.


References

http://wiki.nginx.org/HttpHeadersModule