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.
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:
vmod_cryptovmod_urlplusreq.gracevmod_synthbackend, MSE3vmod_ykeyvmod_mmdb and vmod_utilsreturn(error()) syntax in
vcl_backend_fetch and vcl_backend_responsevarnishncsavmod_strvmod_mse, last_byte_timeout support
for fetchesif-range support in conditional
fetchesvmod_jwtvmod_stale, vmod_sqlite3vmod_tlsvmod_headerplusvmod_resolver and Veribotvmod_brotlivmod_format, a brand-new
varnishscoreboardvmod_staleresp.send_timeout variable,
varnishncsa EPOCH support, and the introduction of
utils.waitinglist() and utils.backend_misses()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.
At its base vmod_crypto consists of a set of cryptographic functions
that perform various tasks.
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==.
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.
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.
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 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.
req.gracereq.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;.
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.
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:
myenv/var/lib/mse/book1 and
/var/lib/mse/book2/var/lib/mse/stores/disk1/store-1-1.dat and
/var/lib/mse/stores/disk2/store-1-2.dat/var/lib/mse/stores/disk3/store-2-1.dat and
/var/lib/mse/stores/disk4/store-2-2.datThere’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.
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_ykeywill 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
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 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.
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 abackend {}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();
}
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"));
}
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 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:
vmod_str functions exampleIn our case, the output will be rts_domv, which is the reverse string
of vmod_str.
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
storesmse.set_stores() : selects one or more stores that match a given tagBy 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.
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.
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
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.
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.
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 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:
vmod_jwt is also able to issue JWTs. In the next example we’re
issuing a token with the following properties:
secret as the secret key1234567890John Doetoken response headerAnd 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");
}
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.
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.
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:
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 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();
}
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 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 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();
	}
}
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);
}
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
varnishscoreboardin more detail in chapter 7.
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:
vmod_aclplusvmod_cookieplusvmod_httpvmod_rewritevmod_sessionAnd the following open source VMODs were also packaged:
vmod_bodyaccessvmod_cookievmod_headervmod_saintmodevmod_tcpvmod_varvmod_vsthrottlevmod_xkeySome of these open source VMODs have since been replaced with an enterprise equivalent, but are still available for backwards compatibility reasons.
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.