Performing HTTP redirections in Varnish

Tags: vcl (20) redirect (1) synth (1)

Varnish can perform HTTP redirections by returning a synthetic HTTP 301 response containing a Location header with the redirection endpoint.

Synthetic responses are triggered by a return (synth()) statement called from a client-side VCL subroutine. This also requires extending the vcl_synth logic and adding the necessary redirection logic.

HTTP to HTTPS redirections

One of the most common types of HTTP redirection is HTTP to HTTPS.

Here’s an example where an HTTP request is redirected to its HTTPS equivalent:

vcl 4.1;

import proxy;

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

sub vcl_recv {
    if ((req.http.X-Forwarded-Proto && req.http.X-Forwarded-Proto != "https") || 
        (req.http.Scheme && req.http.Scheme != "https")) {
        return (synth(750));
    } elseif (!req.http.X-Forwarded-Proto && !req.http.Scheme && !proxy.is_ssl()) {
        return (synth(750));
    }
}

sub vcl_synth {
    if (resp.status == 750) {
        set resp.status = 301;
        set resp.http.location = "https://" + req.http.Host + req.url;
        set resp.reason = "Moved";
        return (deliver);
    }
}

If the incoming request has an X-Forwarded-Proto header with a value other than https, Varnish will redirect the URL to the HTTPS equivalent. The same applies if the Scheme header is set to a value other than https.

If the request doesn’t contain an X-Forwarded-Proto or a Scheme header, but the connection was made using the PROXY protocol, Varnish can examine the TLV attributes of the PROXY header and determine whether or not the initial connection was encrypted over TLS.

Thanks to vmod_proxy we can extract the TLV attributes and use proxy.is_ssl() to determine whether or not the connection was encrypted using TLS.

Redirecting internally

Usually HTTP redirections are handled by rewrite rules on the origin webserver, but Varnish can perform these redirections as well.

Here’s a VCL example where we redirect requests for /some-page to /another-page:

vcl 4.1;

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

sub vcl_recv {
    if (req.url == "/some-page") {
        return (synth(301, "/another-page"));
    }
}

sub vcl_synth {
    if (resp.status == 301) {
        set resp.http.location = resp.reason;
        set resp.reason = "Moved";
        return (deliver);
    }
}

Redirecting to another site

Not all redirections are internal. Sometimes it is necessary to redirect content to an external resource. This example redirects requests for /external to the https://external.example.com/ resource:

vcl 4.1;

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

sub vcl_recv {
    if (req.url == "/external") {
        return (synth(301,"https://external.example.com/"));
    }
}

sub vcl_synth {
    if (resp.status == 301) {
        set resp.http.location = resp.reason;
        set resp.reason = "Moved";
        return (deliver);
    }
}