Avoiding HTTP to HTTPS redirect loops in Varnish
When your force HTTP to HTTPS redirection in your web server or web application and cache the output, you might get stuck in a redirect loop. Your browser may present the following error message as a result:
Behind the scenes, your browser will receive a 301 Moved Permanently
status code and your browser will follow the URL from the Location
header:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Content-Length: 226
Content-Type: text/html; charset=iso-8859-1
X-Varnish: 2
Age: 0
Via: 1.1 varnish
Despite using an https://
URI scheme, the origin server will continue to issue redirects and you’re in fact stuck in a redirect loop. This loop continues until your browser gives up, at which point the error message will appear.
No native TLS support in Varnish Cache
The open source version of Varnish doesn’t have native TLS support:
- Incoming TLS connections should be terminated by a TLS proxy like Hitch.
- Backend connections to the origin server only support plain HTTP.
This means that your web server or web application only receives plain HTTP requests, regardless of the protocol of the incoming client connection. Even if you terminate an incoming TLS connection, the web application will always keep enforcing HTTPS through a redirection.
HTTPS awareness through the X-Forwarded-Proto header
Thanks to TLS proxy servers like Hitch you can terminate the TLS connection and communicate over plain HTTP to Varnish and from Varnish to the backend, as illustrated in the diagram below:
The goal is to enable HTTPS awareness by setting the URI scheme in the X-Forwarded-Proto
header.
When a TLS connection is made, the following request header should be set:
X-Forwarded-Proto: https
This will allow the origin server to know what the forwarded protocol was and whether or not a redirection should be issued.
Setting the X-Forwarded-Proto header with Hitch
Hitch is our preferred TLS termination solution. Because Hitch is a dedicated TLS proxy, it has no HTTP awareness and cannot set the X-Forwarded-Proto
request header.
If you are using Hitch, you should connect to Varnish over the PROXY protocol.
Thanks to the PROXY protocol, we can use vmod_proxy
in Varnish to extract the TLV attributes and know whether or not the connection was done over TLS.
This is the VCL code to set the X-Forwarded-Proto
header:
vcl 4.1;
import proxy;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
if(!req.http.X-Forwarded-Proto) {
if (proxy.is_ssl()) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}
}
Setting the X-Forwarded-Proto header in HaProxy
If you are using HaProxy as your TLS proxy, you can use the http-request set-header
directive to set the X-Forwarded-Proto
header to https
:
http-request set-header X-Forwarded-Proto https
Setting the X-Forwarded-Proto header in Apache
If you are using Apache as a TLS proxy, you can use the RequestHeader set
directive to set the X-Forwarded-Proto
header to https
:
RequestHeader set X-Forwarded-Proto "http"
Setting the X-Forwarded-Proto header in Nginx
If you are using Nginx as a TLS proxy, you can use the proxy_set_header
directive to set the X-Forwarded-Proto
header to https
:
proxy_set_header X-Forwarded-Proto https;
No standard HTTPS awareness in Varnish
As described in the built-in VCL, Varnish identifies a cached object by a hash that is created using the request URL and the Host
header.
The request URL doesn’t contain the URI scheme, which means by default Varnish has no HTTPS awareness.
By default, Varnish considers the 301
status code to be cacheable and because the origin server continuously issues redirects, the redirection will be cached.
So despite the X-Forwarded-Proto
header being sent, Varnish will still only cache one version. Whether the HTTP version or the HTTPS version is cached depends on what protocol is used for the first request.
- If the HTTP version is requested first, the redirect will be cached and you will still end up in a redirect loop
- If the HTTPS version is cached, plain HTTP requests will return content with HTTPS references which will result in mixed content warnings in your browser
Create cache variations based on the X-Forwarded-Proto header
Enabling HTTPS awareness in Varnish can be done by creating cache variations for every cached object based on the X-Forwarded-Proto
header.
This can be done by return the following HTTP response header:
Vary: X-Forwarded-Proto
Varnish will process this header and will use the value of the X-Forwarded-Proto
request header to extend the hash. This means there will be a cache variation for the HTTP version and a cache variation for the HTTPS version.
You can set this header in your application code, but you can also set it in VCL:
vcl 4.1;
import proxy;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
if(!req.http.X-Forwarded-Proto) {
if (proxy.is_ssl()) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}
}
sub vcl_backend_response {
if(beresp.http.Vary) {
set beresp.http.Vary = beresp.http.Vary + ", X-Forwarded-Proto";
} else {
set beresp.http.Vary = "X-Forwarded-Proto";
}
}