Search

Varnish Enterprise 6

Varnish Enterprise 6

The origin story

The release of Varnish Enterprise 6 was a new starting point for Varnish Software’s commercial version. In fact, it was so significant that it warranted a rebrand from Varnish Cache Plus, to Varnish Enterprise.

The previous version, which was called Varnish Cache Plus 4.1, was based on Varnish Cache 4.1. Although there were plenty of Varnish Cache 5.x releases, there was no commercial equivalent.

Varnish Software’s goal is to release a stable version that can be supported for a long time. The cost of stabilizing the code, and keeping the version up to date was not worth it for a potential Varnish Enterprise 5 release, given the high quality of Varnish Cache Plus 4.1 at the time, and the lack of killer features in Varnish Cache 5. This changed with Varnish Cache 6 when HTTP/2 support was becoming quite stable, and Unix domain sockets provided a considerable performance gain.

Because Varnish Software is an important contributor to the Varnish Cache project, preparations were made long before the actual release of Varnish Cache 6. As a matter of fact, Varnish Enterprise 6.0.0r0 was ready before Varnish Cache 6.0.0 was released. However, it is important to mention that Varnish Enterprise 6.0.0r0 was never released to the public. The first public version was Varnish Enterprise 6.0.1r1 on September 20th 2018.

New features in Varnish Enterprise 6

Now that Varnish Cache Plus had turned into Varnish Enterprise, a couple of major features had to be developed and released on top of this milestone version.

The big one was Massive Storage Engine (MSE) version 3. The first version of MSE was introduced in the days of Varnish Cache Plus 4.0. The second incarnation of MSE debuted with the first public release of Varnish Cache Plus 4.1. With Varnish Enterprise 6, MSE has reached its third iteration, and each new version has improved on the previous ones.

Chapter 7 has a section dedicated to MSE, in which its architecture, configuration, and usage is explained in-depth.

Along with MSE came the release of Ykey, a VMOD that is used for tag-based invalidation of objects in the cache. It is the successor of Xkey, and was specifically developed to work well with MSE.

Another important new feature that was launched was Total Encryption: an end-to-end encryption feature that was written in VCL and leveraged the brand-new vmod_crypto.

But as mentioned before: since Varnish Cache 6 is not a single release, or snapshot, Varnish Enterprise 6 isn’t either. With every release, new features were added.

Here’s a quick overview of some Varnish Enterprise 6 feature additions:

  • Varnish Enterprise 6.0.0r0 (unreleased): Varnish Total Encryption and vmod_crypto
  • Varnish Enterprise 6.0.0r1 (unreleased): vmod_urlplus
  • Varnish Enterprise 6.0.1r1: the return of req.grace
  • Varnish Enterprise 6.0.1r3: vmod_synthbackend, MSE3
  • Varnish Enterprise 6.0.2r1: vmod_ykey
  • Varnish Enterprise 6.0.3r6: Varnish High Availability 6
  • Varnish Enterprise 6.0.3r7: vmod_mmdb and vmod_utils
  • Varnish Enterprise 6.0.4r1: return(error()) syntax in vcl_backend_fetch and vcl_backend_response
  • Varnish Enterprise 6.0.4r2: JSON formatting support in varnishncsa
  • Varnish Enterprise 6.0.4r3: vmod_str
  • Varnish Enterprise 6.0.5r1: vmod_mse, last_byte_timeout support for fetches
  • Varnish Enterprise 6.0.5r2: if-range support in conditional fetches
  • Varnish Enterprise 6.0.6r2: built-in TLS support, memory governor, vmod_jwt
  • Varnish Enterprise 6.0.6r3: vmod_stale, vmod_sqlite3
  • Varnish Enterprise 6.0.6r5: vmod_tls
  • Varnish Enterprise 6.0.6r6: vmod_headerplus
  • Varnish Enterprise 6.0.6r8: vmod_resolver and Veribot
  • Varnish Enterprise 6.0.6r10: vmod_brotli
  • Varnish Enterprise 6.0.7r1: vmod_format, a brand-new varnishscoreboard
  • Varnish Enterprise 6.0.7r2: new counters for vmod_stale
  • Varnish Enterprise 6.0.7r3: a new resp.send_timeout variable, varnishncsa EPOCH support, and the introduction of utils.waitinglist() and utils.backend_misses()
  • Varnish Enterprise 6.0.8r1: std.bytes() was backported from Varnish Cache, introduction of utils.hash_ignore_vary()

It’s important to know that the above feature list only covers the introduction of new features. Every release since Varnish Enterprise 6.0.0r0 has seen improvements and added functionality to one more Varnish Enterprise 6 features.

A feature is never really done, as we’ll always be able to improve as we go.

So let’s talk about individual features and show some VCL code.

Total encryption and vmod_crypto

At its base vmod_crypto consists of a set of cryptographic functions that perform various tasks.

Encoding

One example is the crypto.hex_encode() function that turns a blob into its hexadecimal value.

The VCL snippet below returns the hexadecimal value for a. Because crypto.hex_encode() accepts a blob as an argument, the crypto.blob() function is used to convert the string into the required blob.

vcl 4.1;

import crypto;

sub vcl_recv {
    return(synth(200, crypto.hex_encode(crypto.blob("a"))));
}

As you’d expect, the output returned by this VCL snippet is 61. And using crypto.string(crypto.hex_decode("61")), you can turn the hexadecimal value 61 back into a.

Similarly, we can also base64 encode and decode. Here’s the base64 equivalent:

vcl 4.1;

import crypto;

sub vcl_recv {
    return(synth(200, crypto.base64_encode(crypto.blob("a"))));
}

The output will be YQ==.

Hashing

Besides encoding, there are also hashing functions. The following example will create a sha512 hash of a string:

vcl 4.1;

import crypto;

sub vcl_recv {
    return(synth(200, crypto.hex_encode(crypto.hash(sha512,"password"))));
}

vmod_crypto also supports hash-based message authentication code or HMAC as we call it.

The following example will create an HMAC signature for the string password using the sha512 hash function, and will sign it using the secret key abc123:

vcl 4.1;

import crypto;

sub vcl_recv {
    return(synth(200, crypto.hex_encode(crypto.hmac(sha512,crypto.blob("abc123"),"password"))));
}

The output will be 3e714097c7512f54901239ceceeb8596d2ced28e3b428ed0f82662c69664c11cc483daf01f66671fb9a7a2dac47977f12095dc08e1b2954e698de2220f83b97e.

Encryption

And finally, vmod_crypto also supports encryption and decryption. The following example will return an AES encrypted string using a 16-byte key:

vcl 4.1;

import crypto;

sub vcl_recv {
	crypto.aes_key(crypto.blob("my-16-byte-value"));
	return(synth(200, crypto.hex_encode(crypto.aes_encrypt("password"))));
}

The output will be 60ed8326cfb1ec02359fff4a73fe7e0c. And by calling crypto.aes_decrypt(crypto.hex_decode("60ed8326cfb1ec02359fff4a73fe7e0c")), the encrypted value will be decrypted back to password.

Total Encryption

These cryptographic functions can be used in your VCL code, but they are also leveraged by Total Encryption to provide a service that encrypts your data before it gets stored in cache.

Encrypting your cached data is quite easy, and all the logic is hidden behind this one include statement:

include "total-encryption/random_key.vcl";

This statement will encrypt the response body using crypto.aes_encrypt_response() before storing it in cache. On the way out, it will decrypt the cached response body using crypto.aes_decrypt_response() before sending it to the client.

This approach uses a random key for encryption, where the key is stored in an in-memory key-value store. This is of course not persistent. That isn’t really a problem because the cache itself is, by default, not persistent either.

However, if you use disk persistence in MSE, the key will not survive a restart, while the encrypted content will. We need a more reliable solution for that.

We can create a secret key on disk that is loaded into Varnish using the -E command line option. But first, we need to create the secret key. Here’s a set of Linux commands to achieve this:

$ cat /dev/urandom | head -c 1024 > /etc/varnish/disk_secret
$ sudo chmod 600 /etc/varnish/disk_secret
$ sudo chown root: /etc/varnish/disk_secret

In order to set the -E option, you’ll need to edit your systemd unit file and add the option. Here’s an oversimplified example of how to do this:

ExecStart=/usr/sbin/varnishd ... -E /etc/varnish/disk_secret

But in the end, the only thing you’ll need to add to your VCL file to support persisted data encryption is the following line:

include "total-encryption/secret_key.vcl";

vmod_urlplus

vmod_urlplus is a URL normalization, parsing and manipulation VMOD. It doesn’t just handle the URL path, but also the query string parameters. It has a set of utility functions that make interacting with the URL quite easy.

The following example features a couple of getter functions that retrieve the file extension and filename of a URL:

vcl 4.1;

import urlplus;

sub vcl_backend_response {
	if (urlplus.get_extension() ~ "gif|jpg|jpeg|bmp|png|tiff|tif|img") {
		set beresp.ttl = 1d;
	}
	if (urlplus.get_basename() == "favicon.ico") {
		set beresp.ttl = 1w;
	}
}

The next example is a normalization example in which we remove all query string parameters that start with utm_. Upon rewriting, the query string parameters are sorted alphabetically:

vcl 4.1;

import urlplus;

sub vcl_recv {
	# Remove all Google Analytics
	urlplus.query_delete_regex("utm_");

	# Sort query string and write URL out to req.url
	urlplus.write();
}

Normalizing URLs is useful when query string parameters are added that don’t result in different content. Because items in cache are identified by the URL, removing these query string parameters results in fewer unnecessary cache variations, which increases your hit rate.

The example above explicitly removes query string parameters. Instead of stating which parameters should be removed, we can also state which ones we want to keep. The following example illustrates this feature:

vcl 4.1;

import urlplus;

sub vcl_recv {
	# Only keep id query string parameter
	urlplus.query_keep("id");

	# Sort query string and write URL out to req.url
	urlplus.write();
}

This example will remove all query string parameters, except the ones that were kept. In this case, only id will be kept.

The return of req.grace

req.grace is a VCL request variable that specifies the upper limit on the object grace.

Grace mode is a concept where expired objects are served from cache for a certain amount of time while a new version of the object is asynchronously fetched from the backend.

This feature was removed from Varnish in the 4.0.0 release, back in 2014, and was reintroduced in the 6.0.1 release in 2018.

Here’s how to use it in your VCL code:

vcl 4.1;

import urlplus;
import std;

sub vcl_recv {
	if (std.healthy(req.backend_hint)) {
		set req.grace = 10s;
	}
}

sub vcl_backend_response {
	set beresp.grace = 1h;
}

So in this case we’re setting the object’s grace to an hour via set beresp.grace = 1h;, but as long as the backend is healthy, we’re only allowing ten seconds of grace, via set req.grace = 10s;.

vmod_synthbackend

Varnish can return synthetic responses. These are HTTP responses that didn’t originate from an actual backend request. The standard return(synth(200,"OK")); VCL implementation does a pretty decent job at this. Unfortunately, these responses are generated on-the-fly, and aren’t cacheable.

vmod_synthbackend is a module that creates cacheable synthetic responses. Here’s the VCL code for it:

vcl 4.1;

import synthbackend;

backend default none;

sub vcl_backend_fetch {
	set bereq.backend = synthbackend.from_string("URL: " + bereq.url + ", time: " + now);
}

The above example will print the current URL and the current timestamp. The response will be stored in cache using the default TTL.

Because backend requests and responses are synthetic, there is no need to define a real backend. Instead you can use backend default none; to set a pro forma backend called default.

MSE3

Version 3 of the Massive Storage Engine adds a lot more reliability and flexibility to the product. Chapter 7 has a section dedicated to MSE, so I won’t cover too many details here.

I’ll just throw in an example configuration file that shows the capabilities of MSE:

env: {
	id = "myenv";
	memcache_size = "100GB";

	books = ( {
		id = "book1";
		directory = "/var/lib/mse/book1";
		database_size = "1G";

		stores = ( {
			id = "store-1-1";
			filename = "/var/lib/mse/stores/disk1/store-1-1.dat";
			size = "1T";
		}, {
			id = "store-1-2";
			filename = "/var/lib/mse/stores/disk2/store-1-2.dat";
			size = "1T";
		} );
	}, {
		id = "book2";
		directory = "/var/lib/mse/book2";
		database_size = "1G";

		stores = ( {
			id = "store-2-1";
			filename = "/var/lib/mse/stores/disk3/store-2-1.dat";
			size = "1T";
		}, {
			id = "store-2-2";
			filename = "/var/lib/mse/stores/disk4/store-2-2.dat";
			size = "1T";
		} );
	} );
};

Here’s what this configuration file defines:

  • My environment is called myenv
  • The environment has 100 GB of memory to store objects
  • The environment has two books, which are databases where metadata is stored
  • Both books are 1 GB in size and are stored in /var/lib/mse/book1 and /var/lib/mse/book2
  • Each book has two stores, these are pre-allocated large files where objects are persisted
  • Each store is 1 TB in size
  • The stores for book 1 are stored in /var/lib/mse/stores/disk1/store-1-1.dat and /var/lib/mse/stores/disk2/store-1-2.dat
  • The stores for book 2 are stored in /var/lib/mse/stores/disk3/store-2-1.dat and /var/lib/mse/stores/disk4/store-2-2.dat

There’s a total of 4 TB for object storage, and 2 GB for metadata. Book and store allocation happens on a round-robin basis.

A specialized mkfs.mse program, which is shipped with your Varnish Enterprise 6 installation, can be used to initialize all the files from the configuration file. This can be done as follows:

mkfs.mse -c /etc/varnish/mse.conf

Once these files have been initialized, it’s a matter of linking the configuration file to Varnish using the -s option, as illustrated below:

ExecStart=/usr/sbin/varnishd ... -s mse,/etc/varnish/mse.conf

This is another relatively simple example. Because we will be going into much more detail about MSE in chapter 7, it suffices to understand that MSE is a very powerful stevedore that stores objects and metadata.

It combines the raw speed of memory with the reliability of persistent disk storage. It’s truly a best of both worlds implementation that overcomes the typical limitations and performance penalties of disk storage.

vmod_ykey

As described earlier, vmod_ykey is the successor to vmod_xkey. It adds secondary keys to objects, which allows us to invalidate objects based on tags, rather than relying on the URL.

For implementations where content appears on many different URLs, it’s sometimes hard to keep track of the URLs that need to be invalidated. vmod_ykey allows you to add tags to objects, and then invalidate objects based on those tags.

vmod_ykey will be covered more extensively in chapter 6. So let’s keep it simple for now, and throw in one short VCL example:

vcl 4.1;

import ykey;

acl purgers { "127.0.0.1"; }

sub vcl_recv {
	if (req.method == "PURGE") {
		if (client.ip !~ purgers) {
			return (synth(403, "Forbidden"));
		}
		set req.http.n-gone = ykey.purge_header(req.http.Ykey-Purge, sep=" ");
		return (synth(200, "Invalidated "+req.http.n-gone+" objects"));
	}
}

sub vcl_backend_response {
	ykey.add_header(beresp.http.Ykey);
	if (bereq.url ~ "^/content/image/") {
		ykey.add_key("image");
	}
}

This example will add the image tag to all objects that match URLs starting with /content/image/. The origin server, or application, can also issue tags via the custom Ykey response header. This is processed via ykey.add_header(beresp.http.Ykey);.

The logic in vcl_recv will process the invalidation by accepting HTTP requests using the PURGE request method, based on an access control list. The value of the custom Ykey-Purge header, is the tag that will be invalidated.

The HTTP request below can be used to invalidate all objects that match the image tag:

PURGE / HTTP/1.1
Ykey-Purge: image

Varnish High Availability 6

A new and improved version of Varnish High Availability was developed for Varnish Enterprise 6. Instead of relying on a dedicated agent for cache replication, VHA6 leverages the Varnish Broadcaster, which is an existing component of the Varnish Enterprise stack.

By eating our own proverbial dog food, the implementation of VHA6 is a lot simpler, and is written in VCL.

Chapter 7 will feature high availability in depth. For the sake of simplicity, a single VCL example will do for now.

Here’s how you enable Varnish High Availability in Varnish Enterprise 6:

include "vha6/vha_auto.vcl";

sub vcl_init {
	vha6_opts.set("token", "secret123");
	call vha6_token_init;
}

The complexity is hidden behind the included file. And although there are plenty of configurable options, the secret token is the only value that needs to be set. If the Broadcaster’s nodes.conf node inventory file is properly configured, VHA6 will automatically synchronize newly stored objects with other Varnish nodes in the cluster.

vmod_mmdb

vmod_mmdb is the successor to vmod_geoip. Both VMODs are able to map a geographical location to an IP address, based on a database. vmod_mmdb came into play when libgeoip, the library that vmod_geoip depended on, was deprecated.

The previous database format had also been deprecated. vmod_mmdb supports the new libmaxminddb library, and the new database format that comes with it. As a bonus, this new geolocation module can retrieve a lot more information from an IP address than just the country information.

Here’s a VCL example in which we retrieve both the country and city information from the client IP address:

vcl 4.1;

import mmdb;

sub vcl_init {
	new geodb = mmdb.init("/path/to/database");
}

sub vcl_recv {
	return(synth(200, 
		"Country: " + geodb.lookup(client.ip, "country/names/en") + " - " +
		"City: " + geodb.lookup(client.ip, "city/names/en")
	));
}

MaxMind, the company that provides the library and the database, has a free offering. However, retrieving more detailed information will probably require a commercial license.

vmod_utils

As the name indicates, vmod_utils is a utility module that groups features that are too small to deserve their own VMOD.

Without any further ado, let’s just dive into a couple of examples:

The following example uses utils.newline() to print a new line between first line and second line:

vcl 4.1;

import utils;

sub vcl_recv {
	return(synth(200, "first line" + utils.newline() + "second line"));
}

The next example prints a timestamp in a specific date-time format. It is basically a wrapper around the strftime function in the C language:

vcl 4.1;

import utils;

sub vcl_recv {
	return(synth(200, utils.time_format("%A %B%e %Y")));
}

The output will be something like Monday June 8 2020.

Another useful example leverages utils.waitinglist() and utils.backend_misses() to ensure we only cache objects on the second miss:

vcl 4.1;

import utils;

sub vcl_backend_response {
	if (!utils.waitinglist() && utils.backend_misses() == 0) {
		set beresp.uncacheable = true;
		set beresp.ttl = 24h;
	}
}

When utils.backend``_misses() is 0, the object is not yet in hit-for-miss status. This allows us to make it uncacheable until the next time a miss happens, in which case utils.backend_misses() will be 1.

But by adding the !utils``.waitinglist() check, we make sure we only trigger a hit-for-miss when no other requests are on the waiting list. Otherwise these requests would not benefit from request coalescing and request serialization would occur.

Although vmod_utils has plenty more functions, here’s just one more example. It’s the utils.dyn_probe() function that creates a dynamic probe.

Dynamic probes are not predefined using a probe {} construct, but can be set on-the-fly. They are used to probe dynamic backends. Just like these dynamic probes, dynamic backends aren’t predefined using a backend {} construct, but can be set on-the-fly.

vcl 4.1;

import utils;
import goto;

backend default none;

sub vcl_init {
	# set the probe URL to perform health checks
	new dyn_probe = utils.dyn_probe(url="/");
	# assign dynamic probe to dynamic backend definition
	new dyn_dir = goto.dns_director("example.com", probe=dyn_probe.probe());
}

sub vcl_backend_fetch {
	set bereq.backend = dyn_dir.backend();
}

Explicitly return errors

With return(error()), you can explicitly return an error in both vcl_backend_fetch and vcl_backend_response subroutines. This immediately takes you into the vcl_backend_error state, where the error message is returned to the client.

Here’s an example where the error is returned before a backend request is sent:

vcl 4.1;

sub vcl_backend_fetch {
	return(error(500,"Something is wrong here"));
}

Here’s an example where the error is returned after a backend response was received. Even if the backend response turned out to be successful, we can still decide to return an error:

vcl 4.1;

sub vcl_backend_response {
	return(error(500,"Something is wrong here"));
}

JSON formatting support in varnishncsa

varnishncsa is a program that ships with Varnish and that returns access logs in an NCSA format. This is pretty much the standard format that most web servers use.

The new -j flag doesn’t convert the output into JSON, as you might expect. It actually makes the output JSON safe.

Here’s an example of varnishncsa output without any extra formatting:

$ varnishncsa
172.18.0.1 - - [08/Jun/2020:15:47:46 +0000] "GET http://localhost/ HTTP/1.1" 200 0 "-" "curl"

When we actually create a custom output format using -F that looks like JSON, the -j option will make sure the output is JSON safe. Here’s an example:

$ varnishncsa -j -F '{ "received_at": "%t", "response_bytes": %b, "request_bytes": %I, "time_taken": %D, "first_line": "%r", "status": %s }'
{ "received_at": "[08/Jun/2020:16:03:33 +0000]", "response_bytes": 5490, "request_bytes": 472, "time_taken": 155, "first_line": "GET http://localhost/ HTTP/1.1", "status": 200 }

vmod_str

vmod_str is a string VMOD that contains a collection of helper functions. I’ll highlight a couple of these helper functions in a single VCL example:

vcl 4.1;

import str;

sub vcl_recv {
	set req.http.x-str = "vmod_str functions example";

	if (str.len(req.http.x-str) <= 0) {
		return(synth(500,"String is empty"));
	}

	if (str.contains(req.http.x-str," ")) {
		set req.http.x-output = str.split(req.http.x-str,1," ");
	} else {
		set req.http.x-output = req.http.x-str;
	}

	set req.http.x-output = str.reverse(req.http.x-output);

	return(synth(200,req.http.x-output));
}

Here’s a quick rundown of what this VCL file does:

  • The input string we are inspecting and modifying contains vmod_str functions example
  • If this input string is empty, or the header is not set, returns an error
  • If the input string contains spaces, splits the string on spaces, and keeps only the first word
  • Otherwise just uses the input string as-is
  • Reverses the characters and returns the string

In our case, the output will be rts_domv, which is the reverse string of vmod_str.

vmod_mse

vmod_mse gives users control over how MSE behaves on a per request basis in VCL. Although MSE works fine without this VMOD, it does offer fine-grained control to users that require it.

This VMOD has two functions:

  • mse.set_weighting() : set the algorithm that is used for filling stores
  • mse.set_stores() : selects one or more stores that match a given tag
Set weighting algorithm

By setting the weighting algorithm, MSE will switch from basic round robin store selection, to weighted round robin. The example below uses the store size as the weight:

vcl 4.1;

import mse;

sub vcl_backend_response {
	mse.set_weighting(size);
}

This means that MSE will store more objects in the stores that have more space, rather than in the smaller stores. The effect is that different-sized stores will become full, and least recently used nuking will start, roughly at the same time.

By setting mse.set_weighting(available);, MSE will give store more objects in stores that have most space available.

When setting mse.set_weighting(smooth);, MSE will combine store size and available store space to come up with a weight.

Select stores by tag

In order to select stores by tag, the stores will need to be tagged in the MSE configuration file. Here’s such a file:

env: {
	id = "myenv";
	memcache_size = "100GB";

	books = ( {
		id = "book1";
		directory = "/var/lib/mse/book1";
		database_size = "1G";

		stores = ( {
			id = "store1";
			filename = "/var/lib/mse/stores/disk1/store1.dat";
			size = "1T";
			tags = "sata";
		}, {
			id = "store2";
			filename = "/var/lib/mse/stores/disk2/store2.dat";
			size = "1T";
			tags = "ssd";
		} );
	});
	default_stores = "none";
};

This configuration has two stores, each with their own tag. In this case, the tags represent the type of disk they’re hosted on: store 1 has a slower SATA disk and is tagged as sata. store 2 has a faster SSD disk and is tagged as ssd.

If no tag is explicitly set, no store will be selected, and the objects will be stored in memory only. This behavior is the result of default_stores = "none";.

Once the tags have been assigned to their corresponding store, we can start setting tags for specific content, as illustrated in the VCL example below:

vcl 4.1;

import mse;
import std;

sub vcl_backend_response {
	if (beresp.ttl < 120s) {
		mse.set_stores("none");
	} else {
		if (beresp.http.Transfer-Encoding ~ "chunked" || 
		std.integer(beresp.http.Content-Length,0) > std.bytes("1M")) {
			mse.set_stores("sata");
		} else {
			mse.set_stores("ssd");
		}
	}
}

This VCL file will keep short-lived content with a TTL of less than 120 seconds in memory only.

When an object has a TTL that is greater than two minutes, it will be persisted to disk. Based on the content size, the VCL file will either set the sata tag, or the ssd tag.

In this case, Varnish will store objects with a content size bigger than 1 MB on MSE stores that are tagged with the sata tag. The same applies when there’s no Content-Length header and chunked transfer encoding is used.

All other content will be stored on MSE stores that are tagged with the ssd tag.

Last byte timeout

When configuring timeouts for backend interaction, the first byte timeout is a very common parameter to tune: it represents the amount of time Varnish is willing to wait before the backend sends the first byte.

The time to first byte indicates how fast, or how slow, a backend application is able to respond.

With the introduction of the last byte timeout, Varnish can now decide how long it is willing to wait for the last byte to be received. When this timeout occurs, it usually means there is network latency. But when chunked transfer encoding is used, it can mean that it takes too long for the last chunk to be sent and received.

You can either define this using a backend definition in your VCL file:

backend default {
	.host = "localhost";
	.port = "8080";
	.last_byte_timeout = 90s;
}

Or you can explicitly set this value in your vcl_backend_fetch logic:

vcl 4.1;

sub vcl_backend_fetch {
	set bereq.last_byte_timeout = 90s;
}

The code above overrides any timeout set in the backend definition. It also overrides the default timeout, which is 0, meaning it will wait forever. The default timeout can be changed with the last_byte_timeout runtime parameter. Like all runtime parameters, the change can be made persistent by adding it to the command line in your service file:

ExecStart=/usr/sbin/varnishd ... -p last_byte_timeout=90

If-Range support

Range requests are a common HTTP feature, which Varnish supports as well.

By issuing a Range header upon request, a client can request a selected range of bytes to be returned, instead of the full body. The response will not have an HTTP 200 OK status, but an HTTP 206 Partial Content status code.

Varnish Enterprise 6 now also supports the If-Range request header in conditional fetches. This means when a range request is satisfiable, and the If-Range request header contains a value that matches either the Etag response header, or the Last-Modified response header, the range will be returned with an HTTP 206 Partial Content status code.

If the If-Range header doesn’t match any of these response headers, the full response body is returned with an HTTP 200 OK status code.

Built-in TLS support

Up until Varnish Enterprise 6.0.6r2, no version of Varnish has ever had native support for TLS: neither Varnish Enterprise, nor Varnish Cache. The replacement for built-in TLS has been to use a TLS terminator like Hitch, which communicates connection meta information to Varnish through the PROXY protocol.

Varnish Enterprise now has native TLS support, which largely eliminates the need for a TLS proxy. TLS configuration is done in a separate file, as illustrated below:

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

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

That configuration file is then linked to the varnishd process by using the -A command line parameter. Here’s an example:

ExecStart=/usr/sbin/varnishd ... -A /etc/varnish/tls.cfg

The TLS configuration syntax is the same that Hitch uses. Hitch is a dedicated TLS proxy, developed by Varnish Software. The configuration syntax is the same, which allows for a seamless transition from Hitch to native TLS.

Memory governor

The memory governor is a new feature of the Massive Storage Engine.

Correctly sizing the memory of your Varnish server can be tricky at times, especially at high scale. Besides the size of the payload in your cache, other support data structures and temporary workspaces for handling requests use a varying amount of memory. In addition to this, transient storage for short-lived objects is also allocated separately.

Even if only 80% of the server’s memory is allocated for cache payload data, the server can still run out of memory when a large number of clients is connected, or when the individual objects are small.

The memory governor alleviates this issue by automatically adjusting the size of the cache based on the size of other allocations needed to run Varnish comfortably. The memory governor allows MSE to limit the memory of the varnishd process, instead of just the size of the payload data.

The memory governor is enabled by setting MSE’s memcache_size property to auto:

env: {
    id = "myenv";
    memcache_size = "auto";
};

The memory_target runtime parameter limits the total size of the varnishd process. Its default value is 80%. You can set the value to percentages, or absolute memory sizes.

vmod_jwt

vmod_jwt is a module for creating, manipulating, and verifying JSON Web Tokens and JSON Web Signatures.

JWT is a standard that APIs use to create and verify access tokens: the API creates the token, and the client sends it back to the API on every subsequent request to identify itself.

The JWT contains a set of public claims that the API can use. In order to guarantee the integrity of a token, the JWT also contains an HMAC signature that the API verifies.

vmod_jwt can verify incoming JSON Web Tokens, as illustrated in the example below:

vcl 4.1;

import jwt;

sub vcl_init {
	new jwt_reader = jwt.reader();
}

sub vcl_recv {
	if (!jwt_reader.parse(regsub(req.http.Authorization,"^Bearer ",""))) {
		return (synth(401, "Invalid JWT Token"));
	}

	if (!jwt_reader.set_key("secret")) {
		return (synth(401, "Invalid JWT Token"));
	}

	if (!jwt_reader.verify("HS256")) {
		return (synth(401, "Invalid JWT Token"));
	}
}

This example will look for an Authorization request header that matches Bearer, and then performs the following tasks:

  • The token is extracted and parsed
  • The secret key is set
  • The token is verified using a SHA256 HMAC signature

vmod_jwt is also able to issue JWTs. In the next example we’re issuing a token with the following properties:

  • A SHA256 HMAC signature with secret as the secret key
  • The subject is 1234567890
  • The issuer is John Doe
  • The token is stored in the token response header

And here’s the code to issue the JWT using vmod_jwt:

vcl 4.1;

import jwt;

sub vcl_init {
	new jwt_writer = jwt.writer();
}

sub vcl_backend_response {
	jwt_writer.set_alg("HS256");
	jwt_writer.set_sub("1234567890");
	jwt_writer.set_iss("John Doe");
	set beresp.http.token = jwt_writer.generate("secret");
}

vmod_stale

The new VMOD stale can be used to cleanly implement stale if error behavior in Varnish. Varnish’s built-in grace mode covers the more well-known stale while revalidate caching behavior, but does not work well to implement a stale if error.

Stale while revalidate will keep stale content around while Varnish asynchronously gets the most recent version of the object from the cache. This can result in outdated content being served briefly.

This VMOD allows you to have a stale if error without stale while revalidate. This means: never serve outdated content, except when the backend is down.

The example below will revive a stale object when the backend returns a status code greater than or equal to 500:

vcl 4.1;

import stale;

sub stale_if_error {
	if (beresp.status >= 500 && stale.exists()) {
		# Tune this value to match your traffic and caching patterns
		stale.revive(20m, 1h);
		stale.deliver();
		return (abandon);
	}
}

sub vcl_backend_response {
	set beresp.keep = 1d;
	call stale_if_error;
}

sub vcl_backend_error {
	call stale_if_error;
}

This stale.revive() function will set a new TTL and a new grace value, as long as the total remaining lifetime of the object (TTL + grace value + keep value) is not exceeded. If any of these values exceed the total lifetime, the maximum remaining lifetime is used for either the TTL or the grace value, and the rest flows over into the keep value.

More information about the object lifetime, time to live, grace, and keep can be found in chapter 3.

vmod_sqlite3

SQLite is a library that implements a serverless, self-contained relational database system. As the name indicates, it’s very lightweight, and relies on a single database file.

vmod_sqlite uses this library to offer SQLite support in Varnish. The following example uses vmod_sqlite to display the user’s name based on an ID cookie:

vcl 4.1;

import sqlite3;
import cookieplus;

backend default none;

sub vcl_init {
	sqlite3.open("sqlite.db", "|;");
	sqlite3.exec({"CREATE TABLE `users` (`name` VARCHAR(100));"});
	sqlite3.exec("INSERT INTO `users` VALUES ('John'), ('Marc'), ('Charles'),('Mary');");
}

sub vcl_fini {
	sqlite3.close();
}

sub vcl_recv {
	unset req.http.userid;
	unset req.http.username;
	cookieplus.keep("id");
	cookieplus.write();
	if (cookieplus.get("id") ~ "^[0-9]+$") {
		set req.http.userid = cookieplus.get("id");
		set req.http.username = sqlite3.exec("SELECT `name` FROM `users` WHERE rowid=" + sqlite3.escape(req.http.userid));
	}
	if (!req.http.username || req.http.username == "") {
		set req.http.username = "guest";   
	}
	return(synth(200,"Welcome " + req.http.username));
}

As you can see, the vcl_init subroutine will initialize the SQLite database that is stored in the sqlite.db file. Various SQL statements are executed to create the users table, and to populate it.

When receiving requests, the value of the ID cookie is used as the row identifier of the record we’re trying to fetch.

If there’s a match, the username is returned, if not, guest is returned.

Take for example the following HTTP request:

GET / HTTP/1.1
Cookie:id=2

The output in this case will be Welcome Marc, because row id 2 in the users table corresponds to Marc.

vmod_tls

Varnish Enterprise 6.0.6r2 features support for native TLS, meaning that TLS connections can be handled by Varnish, instead of depending on external TLS termination.

Up until 6.0.r2, we had to use vmod_proxy to get TLS information for a TLS connection that was terminated elsewhere. Now, we can now use vmod_tls to get information about native TLS connections.

All sorts of TLS information is available:

  • The TLS version
  • The TLS cipher
  • Server Name Indication (SNI) information
  • Application Layer Protocol Negotiation (ALPN) information
  • The certificate signature algorithm
  • The certificate generation algorithm

But most importantly, there’s a. is_tls() function that will return a boolean value to indicate whether or not the connection was made over TLS. Here’s some example code:

vcl 4.1;

import tls;

sub vcl_recv {
	if (!tls.is_tls()) {
		set req.http.location = "https://" + req.http.host + req.url;
		return(synth(750,"Moved Permanently"));
	}
}

sub vcl_synth {
	if (resp.status == 750) {
		set resp.status = 301;
		set resp.http.location = req.http.location;
		return(deliver);
	}
}

This example is a typical use case where connections over plain HTTP automatically get redirected to the HTTPS equivalent.

vmod_headerplus

vmod_headerplus is an advanced header creation, manipulation, and introspection module. It facilitates tasks that otherwise would be done with more complicated regular expressions.

This VMOD allows easy access to complete header values, and specific attributes of a header value.

The first example shows how multiple headers that match a given pattern can be deleted using headerplus.delete_regex():

vcl 4.1;

import headerplus;

sub vcl_recv {
	headerplus.init(req);
	headerplus.delete_regex("^X-");
	headerplus.write();
}

The example will delete all headers that start with X-.

The next example will look at the Cache-Control response header, and will set the max-age attribute to 20 seconds if there’s an s-maxage attribute:

vcl 4.1;

import headerplus;

sub vcl_backend_response {
	headerplus.init(beresp);
	if (headerplus.attr_get("Cache-Control", "s-maxage") {
		headerplus.attr_set("Cache-Control", "max-age","20");
	}
	headerplus.write();
}

vmod_resolver

In Varnish Enterprise 6.0.6r8 vmod_resolver was added. It’s a module that performs Forward Confirmed reverse DNS (FCrDNS). This means that a reverse DNS resolution is done on the client IP address.

The resulting hostname is then resolved with a forward DNS resolution, and if any of the resulting IP addresses match the original client IP address, the check succeeds.

Here’s some VCL to illustrate the feature:

vcl 4.1;

import std;
import resolver;

sub vcl_recv {
	if (resolver.resolve()) {
		std.log("Resolver domain: " + resolver.domain());
	} else {
		std.log("Resolver error: " + resolver.error());
	}
}

Veribot

Veribot is a Varnish Enterprise 6.0.6r8 feature that leverages vmod_resolver. The goal of the Veribot feature is to check whether or not a client is a bot.

It does a first pass with a User-Agent filter, then a second pass using FCrDNS, which is performed by vmod_resolver.

Here’s a VCL example of Veribot:

vcl 4.1;

include "veribot.vcl";

sub vcl_init {
	vb_ua_filter.add_rules(string = {"
		"(?i)(google|bing)bot"
		"(?i)slurp"
	"});

	vb_domain_rules.add_rules(string = {"
		".googlebot.com" "allow"
		".google.com" "allow"
		".bingbot.com" "allow"
		".slurp.yahoo.com" "allow"
		".fakebot.com" "deny"
	"});
}

sub vcl_recv {
	call vb_check_client;
	if (req.http.vb-access != "allow") {
		return(synth(403,"Forbidden"));
	}
}

If the User-Agent header of a client passes the filter, the FCrDNS check succeeds, and the resulting domain name is allowed access by the domain rules, the client will be allowed access to the content.

This example assumes that the content would otherwise be guarded from regular users with a paywall. Verified bots, on the other hand, do get access to the content, and will index it for SEO purposes.

vmod_brotli

vmod_brotli was released in Varnish Enterprise 6.0.6r10, and offers Brotli compression.

Not only can vmod_brotli compress an object in cache using its optimized compression algorithm, it can also send Brotli compressed responses to browsers that support it.

When Brotli compressed responses are sent, the Content-Encoding: br header is also added.

Content from backends that send native Brotli to Varnish, will also be processed by Varnish, and stored in cache in that format.

Here’s some VCL code to show you how to enable Brotli support:

vcl 4.1;

import brotli;

sub vcl_init {
	brotli.init(BOTH, transcode = true);
}

sub vcl_backend_response {
	if (beresp.http.content-encoding ~ "gzip" ||
		beresp.http.content-type ~ "text") {
		brotli.compress();
	}
}

vmod_format

String interpolation is possible in Varnish by closing the string, using the plus sign, and re-opening the string. When many values need to be included, this can become tedious.

vmod_format leverages the ANSI C printf() capabilities to easily perform string interpolation based on ordered arguments.

Imagine the following VCL snippet:

vcl 4.1;

sub vcl_synth {
	set resp.body = "ERROR: " + resp.status +"\nREASON: " + resp.reason + "\n";
	return (deliver);
}

It’s not that complicated, but we still have to open and close the string to insert the values into the string. As the size of the string grows, and the number of interpolated variables increases, things get more complicated.

The following VCL example uses vmod_format to make this task a lot easier:

vcl 4.1;

import format;

sub vcl_synth {
	set resp.body = format.quick("ERROR: %s\nREASON: %s\n",
		resp.status, resp.reason);
	return (deliver);
}

scoreboard

The varnishscoreboard program, which was introduced in Varnish Enterprise 6.0.4r3, and redesigned for Varnish Enterprise 6.0.7r1, displays the current state of the various active threads.

Here’s some example output from this tool:

$ varnishscoreboard
Age      Type     State         Transaction Parent      Address             Description
   1.64s probe    waiting                 0           0 -                   boot.default
   2.11m acceptor accept                  0           0 :6443               a1
   2.11m acceptor accept                  0           0 :6443               a1
   2.11m acceptor accept                  0           0 :80                 a0
   0.03s acceptor accept                  0           0 :80                 a0
   0.01s backend  startfetch         360910      360909 -                   POST example.com /login
   0.01s client   fetch              360909      360908 172.19.0.1:63610    POST example.com /login
   2.11m acceptor accept                  0           0 :6443               a1
   2.11m acceptor accept                  0           0 :6443               a1
   2.11m acceptor accept                  0           0 :80                 a0
   0.01s acceptor accept                  0           0 :80                 a0
Threads running/in pool: 10/90

We’ll cover varnishscoreboard in more detail in chapter 7.

Features ported from Varnish Cache Plus 4.1

Since the release of Varnish Enterprise 6 in 2018, a lot of new features have been added. But the very first features that were added were actually ported from Varnish Cache Plus 4.1. Users were quite accustomed to a range of features, and didn’t want to lose them.

The Varnish Software team ported the following enterprise features:

  • Parallel ESI
  • Edgestash
  • Dynamic backends
  • Backend TLS
  • Key-value store
  • Least connections director
  • Real-time status module
  • Varnish High Availability
  • vmod_aclplus
  • vmod_cookieplus
  • vmod_http
  • vmod_rewrite
  • vmod_session

And the following open source VMODs were also packaged:

  • vmod_bodyaccess
  • vmod_cookie
  • vmod_header
  • vmod_saintmode
  • vmod_tcp
  • vmod_var
  • vmod_vsthrottle
  • vmod_xkey

Some of these open source VMODs have since been replaced with an enterprise equivalent, but are still available for backwards compatibility reasons.

What happens when a new Varnish Cache version is released?

As mentioned before, Varnish Enterprise 6 is built on top of Varnish Cache 6.0, so it follows its releases. These releases are patch releases, so they usually relate to bugfixes or security updates.

When the open source community notices a bug, and it gets fixed in Varnish Cache, the fix will be ported to Varnish Enterprise as well, and the patch version number increases.

On February 4th 2020 for example, a security vulnerability was fixed in Varnish Cache 6. This resulted in the immediate release of version 6.2.3, 6.3.2, and 6.0.6 (LTS). Naturally, a new version of Varnish Enterprise was released and became version 6.0.6r1.

It also works the other way around: sometimes an important fix happens in Varnish Enterprise first, which results in a new release. Then the fix is ported to Varnish Cache, which results in a release as well. Because this new Varnish Cache release doesn’t add anything new at that point, the subsequent patch release of Varnish Enterprise is postponed until there’s enough new additions and fixes to warrant another release.

Because Varnish Enterprise 6 follows the Varnish Cache 6.0 release schedule, new features in other Varnish Cache minor versions are not automatically added to Varnish Enterprise. Feature porting from 6.1, 6.2, 6.3, 6.4, 6.5 and 6.6 happens on a case-by-case basis. Compatibility breaking changes, however subtle they may be, are not ported.


®Varnish Software, Wallingatan 12, 111 60 Stockholm, Organization nr. 556805-6203