Nginx

NGINX: Enable conditional GET for dynamic pages

by ,


What is Conditional GET

Conditional GET is an essential part of HTTP protocol that revolves around ETag and Last-Modified headers. It allows for caches to validate with your server, whether it’s OK to use currently cached version of a web resource.

When a client has already cached a page (most browsers default to cache pages in absence of “negative” Cache-Control header), upon next request it will simply send If-None-Match (If-Modified-Since) request asking whether content has changed.

If the content have not changed, server responds with 304 Not Modified and empty response body. Thus saving time and traffic to download the same stuff all over again!

While most of web servers have ability to send the ETag (or Last-Modified) headers for static assets, only few web applications leverage those headers to improve their performance. Which means that the main page resources, e.g. PHP generated HTML documents, are not tagged properly, so they do not benefit from conditional requests at all.

10 years ago, I did blog about enabling Zend Framework with conditional GET. But is there a more universal approach?

To implement conditional GET universally means that the web server (not the app!) will take some hash of request’s response and put it into ETag header. It should then be able to handle If-None-Match requests and emit a 304 response if the ETag value provided by clients matches.

ngx_dynamic_etag

In my continuous journey of expanding nginx-extras collection, I have stumbled upon what seems to be an abandoned module, nginx-dynamic-etags. Something so useful for performance reasons, or rather golden in the age of non-stop chasing for performance tricks.

And so I’ve forked it as ngx_dynamic_etag which can be easily installed using our repository.

While being a novice to NGINX programming, I think I got many obvious issues sorted out. Mainly:

  • Offload logic of matching ETag to NGINX core, here by setting r->disable_not_modified = 0;
  • Only generate ETag in locations where it is desired, here
  • When your app already sends an ETag, it does not tamper with it, here

And of course, having it compiled as dynamic module and making it more lightweight / configurable through ETag-ging only HTML responses by default.

How to install dynamic ETag module in CentOS / ReHat 7

It is as easy as you thought, if you already used my repository:

Step 1. Add GetPageSpeed extras YUM repository

Run the following command to add our repository:

yum install https://extras.getpagespeed.com/release-el7-latest.rpm

Step 2. Install NGINX and ETag module

Then, all you have to do to install NGINX with ETag module:

yum install nginx nginx-module-dynamic-etag

Follow the installation prompt to import GPG public key that is used for verifying packages.

Step 3. Enable dynamic ETag support in NGINX

Simply add the following at the top of your /etc/nginx/nginx.conf:

load_module modules/ngx_http_dynamic_etag_module.so;

Now that NGINX has dynamic ETag capability, you can actually enable it for a location. An obvious one is PHP handler, so:

http {
    server {
        location ~ \.php$ {
            dynamic_etag on;
            fastcgi_pass ...;
        }
    }
}

Verify

To check that your page sends ETag, you can use curl:

curl -IL https://example.com/ | grep etag

Conditional GET requests can be easily verified in Chrome developer tools. Second reload of a cache-friendly resource will result in 304 (not modified) status:

That’s all there is to it. Now NGINX will generate ETag for any HTML document produced by PHP!


Also published on Medium.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.