March 22, 2022
4 min read time

Two-Minute Tech Tuesdays - Redirect Loops

redirect_loops_2mtt

In this week’s episode of Two-Minute Tech Tuesday, we'll talk about getting stuck in an infinite redirect loop, an issue that occurs quite frequently in Varnish when you use caching, and HTTP to HTTPS redirection. In this blog, we'll explain how the issue occurs, and how you can resolve it.

 

 

Imagine getting an error message because a redirect loop was detected. When you check the details, you can see all the 301 redirects, and despite using HTTPS, you still get redirected to that same HTTPS resource.

 

Screen Shot 2022-03-22 at 7.56.58 AM


Although it doesn't make sense at first, when you look at the topology of your setup, it starts becoming clear. The web server has no prior knowledge of the original client connection being done over HTTPS. Because the TLS proxy terminates the TLS connection and uses plain HTTP in the back, the web server only sees Varnish, only sees that plain HTTP request, and issues the 301 redirect accordingly.

HTTP/1.1 301 Moved Permanently
Location: https://localhost/
Content-Length: 226
Content-Type: text/html; charset=iso-8859-1
X-Varnish: 2
Age: 0
Via: 1.1 varnish

 

The X-Forwarded-Proto header

The solution to that problem is issuing the X-Forwarded-Proto header in your TLS proxy, and also in Varnish if required. That way, when the client connects to the TLS proxy, X-Forwarded-Proto will be set to https. However, if the client does a plain HTTP connection to Varnish, it will either be empty or HTTP.

You need to build in TLS awareness into your application or web server so when a plain HTTP request is received prior to doing the 301 redirect, you can check the value of X-Forwarded-Proto. If it's set to HTTPS, there's no need to redirect. If it's not set or set to HTTP, then you can issue the redirect.

If you're using Hitch, assuming that you connect Hitch to Varnish over the PROXY protocol, you can even use VCL for that (below). You do this by importing the proxy VMOD and using the proxy.is_ssl() function to check whether or not (TLS/)SSL was used and set the values of X-Forwarded-Proto accordingly.

vcl 4.1;

import proxy;

backend default {
.host = "backend.example.com";
.port = "80";
}

sub vcl_recv {
if(proxy.is_ssl()) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}

 

The Vary:X-Forwarded-Proto header

Imagine that the first request you're sending is an HTTP request. The 301 redirect gets returned, but also gets stored in the cache with no distinction between the protocols. So if the next request is an HTTPS request, you still get the 301.

The way to tackle this is by issuing a Vary header. In our use case, the Vary header should be set the X-Forwarded-Proto, which means that Varnish should create a cache variation based on the value of the X-Forwarded-Proto request header. This means a variation for HTTPS where the content gets served and a variation for HTTP where the 301 redirect gets stored.

You can set this in your web application, web server, or even in VCL using the following code:

vcl 4.1;

import proxy;

backend default {
.host = "backend.example.com";
.port = "80";
}

sub vcl_recv {
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";
}
}

 

Redirect loops occur a lot more than you would expect, so it makes a lot of sense to issue Vary: X-Forwarded-Proto. It also makes a lot of sense to set the X-Forwarded-Proto value accordingly in your TLS proxy.

Join us next week for another Two-Minute Tuesday topic, presented to you in 2 minutes or less!

varnish_6_by_example_download