VCL is so good it can replace itself

The helicopter view

  • /etc/varnish/default.vcl: our read-only VCL framework, it'll define a few behaviors that you can trigger and configure via the rules file.
  • /etc/varnish/backend: a simple file containing the domain name or IP of the backend.
  • /etc/varnish/rules: the meat of the configuration.

Backend file

Rules file

prefix "/supersecret/" "block"    "404"
prefix "/admin/" "pass"
regex "^/static/(.*)" "redirect" "301" "\1"
suffix ".jpg" "cache" "5m" "1h" "2d"
suffix ".js" "cache" "1m"
  • The first element is an unquoted word describing how we’ll match the second element (prefix, suffix, globbing, regex, etc.)
  • Right after, we have a quoted string that we are going to try and match against the request URL.
  • The last mandatory element is also a quoted string, the command. It’ll specify what we are to do with the request. Here we can block, redirect, bypass the cache or cache.
  • The rest of the line is a list of quoted strings that will depend on the command; “redirect” needs a status code (301 or 302 generally) and a new destination while “cache” can have as many as three durations (for TTL, grace and keep) and “pass” has no extra arguments.

VCL file

  • during initialization:
  • load the string inside /etc/varnish/backend and gives it to the dynamic backend director
  • load the rule file (/etc/varnish/ruleset)
  • for each request:
  • use the rule set to find a line matching the request URL (.match())
  • set the “command” request header accordingly.
  • for each command, execute the relevant code: pass, cache with the correct TTL, return a synthetic response, etc.
vcl 4.0;import goto;
import rewrite;
import std;
import urlplus;
backend fake_be { .host = "0"; }sub vcl_init {
new director = goto.dns_director(std.fileread("/tmp/backend"));
new ruleset = rewrite.ruleset("/tmp/ruleset", type = any);
sub vcl_recv {
set req.backend_hint = director.backend();
unset req.http.location; if (ruleset.match(urlplus.url_as_string())) {
set req.http.command = ruleset.rewrite(field = 2, mode = only_matching);
} else {
set req.http.command = "default";
if (req.http.command == "block") {
return (synth(std.integer(ruleset.rewrite(field = 3, mode = only_matching), 404)));
} else if (req.http.command == "pass") {
return (pass);
} else if (req.http.command == "redirect") {
set req.http.location = ruleset.rewrite(field = 4);
return (synth(std.integer(ruleset.rewrite(field = 3, mode = only_matching), 301)));
} else if (req.http.command == "cache") {
unset req.http.cookie;
} else {
set req.http.command = "default";
sub vcl_backend_response {
if (bereq.uncacheable) {
return (deliver);
} else if (bereq.http.command == "cache") {
set beresp.ttl = std.duration(ruleset.rewrite(field = 3, mode = only_matching), 2m);
set beresp.grace = std.duration(ruleset.rewrite(field = 4, mode = only_matching), 0s);
set beresp.keep = std.duration(ruleset.rewrite(field = 5, mode = only_matching), 1h);
unset beresp.http.set-cookie;
sub vcl_synth {
if (req.http.location) {
set resp.http.location = req.http.location;
return (deliver);

Notable details

regextable? matchmap?

  • regsub: return the full string, replacing the first match
  • regsuball: return the full string, replacing all the matches
  • only_matching: only return the rewritten match

Type conversion

  • std.integer(STRING s, INT default): tries to convert s to a string, and returns default if it's not possible.
  • std.duration(STRING s, DURATION default): does the same thing for durations, unsurprisingly.
suffix ".jpg"          "cache"    "5m" "1h" "2d"
suffix ".js" "cache" "1m"

Fake one until you can make one

backend fake_be { .host = "0"; }


varnishncsa -F '%{command}i %U %s'
varnishncsa -F%{VSL:RespHeader:x-cache[1]}x '%{command}i %U %s'

Drawing the line




Varnish Software is the world’s leading provider of open source web application acceleration software.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

1796. Second Largest Digit in a String

SPA vs MPA: Which One is Better For You?

Old Road Town For Loops and es6 forEach helper

How to fix an error about “expect is not defined”

Update multiple rows in SQL with different values at once

Javascript Algorithms: Bubble Sort

Jetpack Compose Ep:3 — Button App

Using Neo4j’s Full-Text Search With GraphQL

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Varnish Software

Varnish Software

Varnish Software is the world’s leading provider of open source web application acceleration software.

More from Medium

Using Objects for Lookups in Javascript

Intro to Koa JS

Event Loops