How to Configure HTTP/2 with Varnish Using Nginx

More and more companies have started utilising HTTP/2 to improve performance of their sites and user experience. It's quite easy to enable HTTP/2, but how can you enable HTTP/2 with SSL if you have Varnish cache in front of your infrastracture? As Varnish 4.* doesn't support SSL, we need to find a way to make all these components work together.

HTTP/2

As you may suppose, the Web moves forward and now we finally have a new version of the HTTP protocol. Its main benefits are:

- Streams and Multiplexing:  a single HTTP/2 connection can contain multiple concurrently open streams. Multiplexing of requests is achieved by having each HTTP request/ response exchange associated with its own stream. Streams are largely independent of each other, so a blocked or stalled request or response does not prevent progress on other streams.

- Header Compression:  in HTTP/1.1, header fields are not compressed and they unnecessarily consume bandwidth and increase latency. In HTTP/2 HPACK, a new compressor was introduced. It  eliminates redundant header fields, limits vulnerability to known security attacks, and has a bounded memory requirement for use in constrained environments.

- Server Pushes:  HTTP/2 allows a server to pre-emptively send (or "push") responses to a client in association with a previous client-initiated request. This can be useful when the server knows the client will need to have those responses available in order to fully process the response to the original request.

Many modern browsers already support HTTP/2, so, by doing a few simple steps to enable it, you'll get desired benefits.

You can also get a better idea of all additions in the official specification:

Hypertext Transfer Protocol Version 2 (HTTP/2) - RFC 7540

HPACK: Header Compression for HTTP/2 - RFC 7541

Why Do You Need HTTP/2?

All the explanations above are great, but, some people prefer to feel or see the results, and only then, they become inspired. The best demo that I've found and been impressed by was created by Akamai. In their demo, they compare the loading of two identical tiled pictures via HTTP/1.1 and HTTP/2. If your browser supports HTTP/2, then, I recommend you check Akamai's demo. Moreover, to deal with HTTP/2, you'll have to enable SSL and it will improve the security of your site.

A Classical Scheme with Varnish

Usually Varnish sits between clients and the web server. It looks like this:

Varnish HTTP/1.1

Here we have a client who requests a page. His request goes to Varnish (which is configured to listen to 80 port). Then Varnish checks if it has a cached object and returns it directly to the client without asking the web server or asks the web server to get the page for a given URL.

The problem here is that Varnish 4.* doesn't support SSL that is required by browsers for HTTP/2 to work, so we can't just ask Varnish to listen to 443 port and change some configuration parameters.

Solution with Nginx

One possible solution to our problem is to add Nginx in front of Varnish. It will be responsible for working via HTTP/2, support SSL and proxy all requests via HTTP/1.1 to Varnish. Our new schema will look like this:

Nginx (SSL HTTP/2) in front of Varnish

Install Nginx

The minimum version must be 1.9.5 (only since this version ngx_http_v2_module module is available). I use Mac OSX, so I'm going to describe the installation process through brew

brew tap homebrew/nginx

We will need http2 and real IP modules. You can check exact option names via:

brew options nginx-full

To install Nginx:

brew install nginx-full --with-http2 --with-realip

You can also add Nginx to autoload:

brew services start homebrew/nginx/nginx-full

SSL Certificate

One more thing we need is an SSL certificate. There are plenty of ways to get one, but now we'll use openssl to generate a certificate:

openssl req -newkey rsa:2048 -sha256 -keyout <PATH WHERE TO PUT THE *.key FILE> -nodes -x509 -days 365 -out <PATH TO PUT THE *.crt FILE>

One more important thing here, you must enter your domain name while asked about Common Name during the certificate generation process. If you use this certificate, the browser will notify you that it doesn't trust it, but it's ok for our test purposes.

Configure Nginx (HTTP/2, SSL)

Ok, now we're ready to configure Nginx:

server {
    # 443 - default port for SSL
    listen 443 ssl http2;
    server_name <SERVER NAME>;
    # Specify the certificate files
    ssl_certificate <PATH TO THE *.crt file>;
    ssl_certificate_key <PATH TO THE *.key file>;

    location / {
        # Set recommended by Nginx version
        proxy_http_version 1.1;

        proxy_pass http://127.0.0.1:80;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header Host $host;
    }
}

Set Up Varnish

Minimal VCL for Varnish is:

vcl 4.0;

backend local {
    .host = "127.0.0.1";
    .port = "8080";
}

Then we need to start Varnish on 80 port:

varnishd -n vtest -f <PATH TO THE *.vcl FILE> -s malloc,512M -a 127.0.0.1:80

Configure Web Server

As a web server I'll use Nginx again. We've already specified a backend in Varnish, so our minimal configuration for Nginx application server is:

server {
    # Listen the port configured in the Varnish *.vcl file.
    listen 8080;

    server_name <THE SAME SERVER NAME>;
    
    # Define trusted addresses that are known to send correct replacement addresses.
    set_real_ip_from   127.0.0.1;
    real_ip_header     X-Forwarded-For;
    real_ip_recursive on;

    #...
}

Test via Chrome

To test that everything works fine, you can install a browser extension called HTTP/2 and SPDY indicator.

What to Expect from Varnish

Accordingly to one of the newest interviews with Varnish Software CTO Per Buer, they work hard in HTTP/2 direction:

One of the challenging moments is ongoing with the move towards HTTP/2 – a significant change for the project. HTTP/2 is a multiplexed, binary framed protocol. Supporting it will require a rewrite of everything that talks to the client and quite a bit of the backend stuff. A major change is concurrency, so instead of the browser opening a number of connections, it will now open only one connection onto which the data will be multiplexed.

H/2 is currently at the top of the list. We’ve spent years doing various changes to Varnish to prepare for H/2. The last two major releases of Varnish Cache have been about preparing for this.

I hope that in a new release of Varnish Cache we will have tools to remove an additional component from our infrastructure, such as an extra Nginx.

Conclusion

It doesn't matter if you use Varnish for caching or Nginx modules, but you definitely need to configure HTTP/2, because it's a low hanging fruit. Moreover, in our simplified example all resources are accessible through 443, 80 and 8080 ports. If you want to restrict access, you'll have to add redirects to https from external IPs via Varnish or Nginx.

See also:

Nginx's HTTP/2 Does Not Work

It may seem trivial to enable HTTP/2 support in Nginx, but there are some caveats you may face. Let's go through the ones I faced during the HTTP/2 configuration for our sites.