Recommendations/Advice for Web Caching with SSL

I've been using Varnish Cache for a few websites. However, I need advice for implementing HTTPS. I am open to alternatives to Varnish Cache. The configuration of Varnish is relatively complex, so perhaps a less advanced alternative would be better suited for my sites.

Cloudflare looks like a viable option, but from what I could see would need a business plan starting from $200/month. I might be mistaken.

Any recommendations?


Using HTTPS on Varnish isn't that hard. Although Varnish doesn't natively offer TLS, it facilitates TLS termination.

In 2015 Varnish released Hitch, a very powerful TLS proxy that handles terminates TLS and forwards unencrypted HTTP traffic to Varnish.

Installing Hitch

You can download the source from the Hitch website and compile it on our server. If you want to use packages to install Hitch, you can run the following command on Debian or Ubuntu:

apt-get install -y hitch

Configuring Hitch

Once you've installed Hitch, open up /etc/hitch/hitch.conf and make sure you use the following configuration:

frontend = {
    host = "*"
    port = "443"
}

backend = "[localhost]:8443"

write-proxy-protocol-v2 = on

pem-file = "/etc/hitch/certs/example.com"

ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"

ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"

tls-protos = TLSv1.2 TLSv1.3

ecdh-curve = "X25519:prime256v1:secp384r1"

prefer-server-ciphers = false

Please put your certificate in /etc/hitch/certs and adjust the pem-file directive in hitch.conf.

Reconfiguring Varnish

Your Varnish runtime configuration probably contains the following listening information:

varnish -a :80

This means Varnish is listening for connections on port 80. To make sure HTTPS works, we'll add another listening port, but with a specific configuration:

varnish -a :80 -a :8443,PROXY

Make sure you reload Varnish after changing the runtime settings.

You'll notice that port 8843 is now also assigned to Varnish. It is not used for standard HTTP, but for HTTP using the PROXY protocol. PROXY protocol support was also enabled in Hitch.

This ensures the original client IP address is passed along to Varnish, regardless of the number of extra hops it has to go through. The original client IP address will be automatically stored in the X-Forwarded-For header by Varnish.

The type of traffic that is processed by port 8443 is HTTP traffic that originated from Hitch and that is in fact terminated HTTPS traffic.

Identifying HTTPS requests

Hitch is a TLS proxy, it doesn't understand HTTP. This means it cannot set the conventional X-Forwarded-Proto header to indicate what kind of traffic was terminated. Luckily we can track which port was used.

The VCL code below detects HTTP/HTTPS requests and assigns the proper X-Forwarded-Proto header.

vcl 4.0;
import std;
sub vcl_recv {
    if (std.port(local.ip) == 8443) {
        set req.http.X-Forwarded-Proto = "https";
    } else {
        set req.http.X-Forwarded-Proto = "http";
    }
}

A lot of frameworks and CMS systems leverage the X-Forwarded-Proto header to automatically build the right URL schemes. The fact that we're setting this header in VCL is very helpful.

Protocol-base cache variations

A cache stores HTTP responses, not HTTP requests. So when an object gets stored in cache, Varnish doesn't know if it came from an HTTP or an HTTPS URL. If we start caching them without extra measures, you can end up getting stuck in an infinite redirection loop if Varnish starts caching the HTTP version of an object.

To avoid this, Varnish needs to have a cache variation per protocol. You can easily instruct Varnish to do so by returning the following HTTP response header in your backend application:

Vary: X-Forwarded-Proto

If for some reason this is not possible, you can also extend the vcl_hash logic in your VCL code. In that case, please add the following snippet:

sub vcl_hash {
  hash_data(req.http.X-Forwarded-Proto);
}