Varnish Explained

Mattias Geniar, Thursday, October 20, 2016

A few months ago, I gave a presentation at LaraconEU in Amsterdam titled "Varnish for PHP developers". The generic title of that presentation is actually Varnish Explained and this is a write-up of that presentation, the video and the slides.

The simplest way to get a grip of the basics of Varnish would be to watch the video recording of that presentation.

Further down you'll find the slides and a transcript of each slide, with accompanying links.

Varnish Explained: presentation video

This is the recording of the talk at LaraconEU on August 2016.

Varnish Explained

Here's a write-up per slide, with the most important topics.

(Alternatively, if you prefer to just skim through the slides, they are up on Speakerdeck too.)

varnish_explained_002

In order to fully understand Varnish, it's vital that you understand the HTTP protocol (at least the basics) and how its HTTP headers are formed and which purpose they serve.

Later on, I'll cover the Varnish internals, cool acronyms like ESI, Grace, TTL & more.


varnish_explained_003

A quick introduction: I'm Mattias Geniar, I'm the Support/Operations manager at Nucleus Hosting in Belgium.

I run the cron.weekly linux & open source newsletter and, when I find the time, host the SysCast podcast.


Part One: What's Varnish?

varnish_explained_004

Let's go.


varnish_explained_005

Varnish can do a lot of things, but it's mostly known as a reverse HTTP proxy. It brands itself as an HTTP accelerator, making HTTP requests faster by caching them.

It can also serve as a TCP load balancer (using pipe in the VCL code, more on that later), an HTTP load balancer or a generic reverse proxy.

It uses the Varnish Configuration Language (VCL) to allow you as a developer or sysadmin to modify the behaviour of Varnish and to play with the HTTP protocol to shape it so it behaves the way you want.

Varnish is also extendable with VMODs, modules that can be loaded by varnish and provide additional functions/methods and functionality you can call upon in your VCL configurations. Think of these like PHP extensions; binary files that are loaded by Varnish that extend the userspace in which you can create VCL configurations.


varnish_explained_006

A couple of important versions: Varnish 3.x is probably the most widely used, but is now EOL (so: no more security updates). Varnish 4.1 is still supported and Varnish 5 came out a few weeks ago (so the slides are slightly outdated).

If you're upgrading from Varnish 3.x to later: beware, the configurations changed drastically. You'll need to update your syntax/methods in Varnish, or Varnish won't start.


varnish_explained_007

Using Varnish in your infrastructure gives you a couple of advantages: performance, scalability, control & security.

Varnish is usually associated with performance, but it greatly increases your options to scale your infrastructure (load balancing, failover backends etc) and adds a security layer right out of the box: you can easily let Varnish protect you from the httpoxy vulnerability or slowloris type attacks.


Part Deux: HTTP Headers

varnish_explained_008

Let's talk HTTP headers.


varnish_explained_009

A quick summary of HTTP headers: there are request and response headers (basically what your User Agent (=browser) requests and what the Server (Apache/Nginx) responds) and Varnish listens to these by default to determine if requests can or should be cached.


varnish_explained_010

Whenever your browser goes to a certain website, it'll a do a few low-level things: it'll resolve the DNS of the hostname you want to browse to, open a TCP connection to that IP and start sending the HTTP request headers.


varnish_explained_011

They are basically a new-line separated key/value pair. The concepts are the same in HTTP/2, except it's binary and more structured.

The browser describes the kind of response it can receive (plain text, compressed), what site it wants to load (because remember, the browser just connected to the IP, it now needs to tell the webserver which website at that IP address it wants to load).


varnish_explained_012

If all goes well, the server will respond with a similar set of headers: the response headers. It will confirm the HTTP protocol (or down/up-grade the protocol), give a status code and describe the response (plain text / compressed).

Good, that was the HTTP reminder -- now let's get to Varnish.

Part Three: How does Varnish work?

varnish_explained_013

Go!


varnish_explained_014

Varnish is a reverse proxy, so it sits between the User Agent (the browser) and the webserver. It makes decisions to either deliver a cached version of the page, or send the request to the backend webserver for processing.

By default, it'll do so by listening to the HTTP headers the client sends as well as the ones the server responds with. But, those usually suck -- creating the need for proper Varnish templates to avoid boilerplate code being re-created all the time.


varnish_explained_015

In a normal scenario, the browser probably connects directly to the webserver, which in turn will let PHP execute the request. The simplest possible setup.


varnish_explained_016

When Varnish is introduced, a few things happen: the webserver is hidden to the user, as that user now only connects to Varnish. Varnish has bound itself on port :80 and the webserver is either running on a different server or on an alternative port. It's Varnish that will make the connection to the webserver if needed, the user has no idea.


varnish_explained_017

If you follow the official Varnish book, the "internals of Varnish" are described as this. While it's 100% correct, if you're new to Varnish, that image does you no good: it's scary, confusing and it teaches you nothing.

Instead of such a flowchart, I prefer to visualise Varnish slightly different.


varnish_explained_018

Let's start with a basic example: a user tries to connect to a server running Varnish. It'll make a connection to the port where Varnish is running and Varnish can start to process the request.


varnish_explained_019

It'll trigger a routine in Varnish called vcl_recv().

It's a routine where you can write custom VCL code to manipulate requests, determine backends, redirect users, ... it gives you full control over the HTTP request of the user.


varnish_explained_020

After that routine, Varnish can do a cache lookup: the page the user is requesting, does it already exist in the cache?


varnish_explained_021

If it does, it can serve that page to the user. Before it does that, it triggers a new internal routine called vcl_deliver(). It's another place in Varnish where you can manipulate the request, change the HTTP headers etc.


varnish_explained_022

Once that request is finished, Varnish sends the response to the user so he can render it on screen.


varnish_explained_023

Of course, not everything is a cache hit: if Varnish does a cache lookup but finds it doesn't have that object/request in the cache, it has to go and fetch that content from its own backend.

To do so, it'll trigger another internal routine called vcl_backend_fetcht().

You guessed it, it's yet another place where you can further manipulate the request before it gets sent to the backend webserver(s).


varnish_explained_024

That routine will eventually call out to another webserver and have it process the request the user made.


varnish_explained_025

Once the response was created on the backend webserver, Varnish will receive it and fire another routine: vcl_backend_response().

Hey, yet another place to play with the HTTP protocol and change the headers etc. before Varnish will process the request internally.


varnish_explained_026

Varnish can then store the item in its cache (if it's cacheable) and deliver it to the client, via the same vcl_deliver() routine.

Pfew, what a ride!


Part Four: The Varnish Configuration Language

varnish_explained_027

Now that you have a visual of the flows within Varnish, let's see what the configurations look like.


varnish_explained_028

The Varnish Configuration Language is a C-like language, which should be familiar to those writing PHP or JavaScript code.

It looks like the screenshot above, where routines are described using the sub keyword. For a complete example, have a look at my varnish 4.x configuration templates.


varnish_explained_029

Each of those routines I described earlier allow you to customise the HTTP request with that VCL language.

To do so, it offers you a few useful objects: req, req.http, ... Each of those objects represent a part of the HTTP protocol. There's req.method to indicate a GET, POST, PUT, PURGE, ...

There's req.http which contains all the raw HTTP headers sent by the client, so there's req.http.host for the Host-header, req.http.User-Agent for the User-Agent (Chrome/Firefox) of the client, ...

Just like the HTTP protocol itself, the HTTP headers are case insensitive. So req.http.HOST is the same as req.http.host. But do yourself a favour, pick a coding/style standard you like and stick to it.

In the example above we tell Varnish, in the vcl_recv() routine (the very first routine to be called by Varnish) to only deal with GET or HEAD requests. Anything with a different method (a POST, PUT, ...) will be sent to the backend webserver(s) via the return (pass); command, indicating that the request should be passed on to the backend.

Further down, we manipulate the cookies the client has set. This is probably the part where Varnish has thought me the most about the HTTP protocol! As a user, every cookie that is set on a particular domain gets sent to the server for every request. It's a single string, semi-colon delimited. You can change the value of cookies in Varnish, remove or add cookies, etc.


varnish_explained_030

You can also use Varnish to be a "dumb load balancer": it can look at the Host header sent by the user and redirect requests to different backends.

This would allow you to set up a single Varnish server which can send requests to multiple backend servers, depending on the site the user is requesting.

The set req.backend = xxx; code instructs varnish to use a particular backend for that HTTP request.


varnish_explained_031

A common task in Varnish is dealing with Cookies (more tips on that later by the way) and removing cookies for objects or HTTP requests that don't need them.

A typical example is content like CSS, JavaScript, images, ... whether cookies are set or not, chances are that the image won't change. So we remove cookies altogether to determine the cacheability of such an object.


varnish_explained_032

Alternatively, we could also modify the response the webserver has sent us, in case it was a cache miss.

If a webserver wants to set a new cookie, it'll send along one or multiple Set-Cookie headers. In Varnish, we can remove those headers by removing the bereq.http.Set-Cookie header, making it look like the webserver never even sent those requests in the first place!


varnish_explained_033

Another interesting trick is to tell Varnish to never cache a response when it's an server error, something with a status code in the HTTP/500 range. By telling it to reply with return (abandon); it'll bail out that routine and exit the flow.


varnish_explained_034

In the vcl_deliver(); routine you have a final place, right before the HTTP response is sent to your user, to add or remove HTTP headers.

I commonly use this to clean up the request: remove sensitive headers (like Apache or PHP version numbers), add some debug headers to indicate if it was a cache miss or hit, ...


varnish_explained_035

Having so many places to tinker with the request and response can make it hard to visualise where changes were happening and what the outcome would/could be. It can also make you question what's the best place to even begin to manipulate such a request.

To be honest: there's no right or wrong answer here. It depends on your setup. But, there are a couple of tricks to help work with Varnish.


varnish_explained_036

For example, let's run through a request in Varnish and see where/how we can play with it.


varnish_explained_037

In this example, a user wants to connect to our server and load the laravel.com homepage. It sends a request to load the / page (homepage) indicating that it's the Chrome User-Agent.

When Varnish receives that request, it'll start its vcl_recv() routine.


varnish_explained_038

In that vcl_recv routine, we can manipulate the request.

For instance, we can change the request so that it no longer wants to load laravel.com but forge.laravel.com, by setting a new value in the req.http.host object.

Important to know here: the user/client has no idea we're doing this. This is purely internally in Varnish. For the user, it still thinks it's going to receive a response for laravel.com!

Varnish will use that new information (the modified req.http.host and req.http.User-Agent) to check its cache if it has a response it can serve to the client. In this example, we'll assume it was a cache miss.


varnish_explained_039

So now Varnish has to fetch the content from its own backend webserver. But because we modified the request in vcl_recv(), Varnish will ask the backend for the website behind forge.laravel.com, claiming to be an IE5 User-Agent.


varnish_explained_040

The webserver does its thing, and if all goes well it can generate the homeapge for forge.laravel.com.

It'll respond with an HTTP/200 status code, indicating everything was OK. It will also set a PHP session ID, tell that it was generated with the Apache Webserver and indicate that this HTTP response should not be cached, by setting a Cache-Control: No-Cache header.


varnish_explained_041

Varnish receives that response and triggers its routine vcl_backend_response(), where we can modify what the webserver sent us.

Even though the webserver said we should not cache the page (remember: Cache-Control: No-Cache), and Varnish would by default listen to that header and not cache the page, we can overwrite it by setting the beresp.ttl object. The TTL -- or Time To Live -- determines how long we can cache that object.

Additionally, we will overwrite the status code that the webserver sent us (HTTP/200) with one that we invented (HTTP/123).

And finally, we'll remove that Laravel session ID that was generated server side by removing the beresp.http.Set-Cookie header.

After all that fiddling with the HTTP response, Varnish will store the object in its cache using our modified parameters. So it'll store that object as an HTTP/123 response without cookies, even though the server generated an HTTP/200, with cookies and clearly indicated the page shouldn't be cached.


varnish_explained_042

So what eventually gets delivered to the client is a page for forge.laravel.com (our client requested laravel.com) with an HTTP status code of HTTP/123 (the server actually said it was an HTTP/200), without cookies being set.

Such Varnish power. Much amaze. So many places to fuck things up.


varnish_explained_043

There's another important Varnish routine though, it's called vcl_hash(). This is where Varnish decides which parameters it will use to determine its cache/hash key. If the key can match an object already in the cache, it will deliver that object.

By default, Varnish looks at 3 key parameters: the Host-header, the URL being requested and the cookies sent by the client.

Behind the scenes, it will take care of the Content-Encoding that clients can request: it'll store a compressed version of every page in memory, but if a client requests a plain text -- non-compressed -- version, it'll decompress it on the way out and deliver a plain text version to the client.

If any of the parameters inside the vcl_hash() routine changes, Varnish will consider it a new HTTP request and the cache lookup will fail.


varnish_explained_044

If you were thinking ahead, you can see a problem with that kind of hash-key determination.

For instance, the URL /?page=1&languange=nl will show the same content as /?language=nl&page=1, but since the order of the parameters is different, the hash will be different. To resolve this, we normalise the incoming requests as much as possible.

Varnish offers a good tool for this called std.querysort(), which will sort the query parameters alphabetically.

Additionally, you might want to strip query parameters that are irrelevant to the backend, like Google Analytics campaign trackers.


varnish_explained_045

Once you normalised the URL, cookies are the Next Big Thing.

Because they are by default used in the hash-key lookup, any change in the cookies client-side will result in a different hash and a new object in the cache.

If every one of your users has a unique PHP session ID, every Cookie set will be different and thus every hash-key will be unique. Just setting a unique PHP session ID for all your users would effectively destroy caching for your site, as every request is unique.

There are a couple of fixes for this, but the biggest one is to only initialise a PHP session when you need it (ie: on the login page), and not for every page. This is called lazy session initialisation.

Alternatively, some XSRF implementations rely on cookies and should be unique per user too.


varnish_explained_046

Varnish offers an interesting VMOD (extension to Varnish) to help manipulate Cookies.

The cookie.parse object allows to you do interesting things like whitelisting cookies in Varnish, without crazy mind-breaking regular expressions.


varnish_explained_047

In addition, the Host header is also a key in your hashing. So a request for laravel.com and www.laravel.com, even though they're probably the same site, would result in 2 different cached objects in Varnish.

To help Varnish, it's best to redirect users to a single domain and not keep multiple possible domains that would result in the same content being generated.

There are plenty of SEO & technical reasons this is a good move too.

The code example above detects a page without the www-subdomain and triggers a synthetic response in Varnish, via the new vcl_synth() routine.

In that routine, you can detect the resp.status object and create logic handling that differently. In this case, we set a custom error code of 720 in order to replace the Location block in the HTTP response and force a 301 redirect.


varnish_explained_048

That same vcl_hash() can be expended to take any HTTP header into account when determining the cache key. It can add Authorization headers for basic HTTP authentication, custom headers for your API, ...

We also use it at Nucleus to separate cache responses for HTTP vs. HTTPS content, since we often use an Nginx TLS proxy in front of Varnish.


Part Five: Flushing the Cache

varnish_explained_049

There are actually more than 2 hard things in computer science: naming things, cache invalidation, off-by-one errors, DNS, Docker, ...


varnish_explained_050

There are basically 3 ways to flush a cache: the old PURGE HTTP method (which is basically obsoleted), the newer "ban" method (suggested) or the absolute lazy way: restart your varnish service.


varnish_explained_051

The HTTP PURGE method is a "special" HTTP request where you can set the method to PURGE. That in and of itself doesn't actually do anything, but it allows you to catch that special method in varnish's vcl_recv() routine (the very first routine Varnish will call when it receives a request) and trigger an internal cache flush via the return (purge); call.


varnish_explained_052

The preferred way is actually to use "bans": this uses the Varnish Administration CLI tool called varnishadm to connect to a running Varnish daemon on its administrative port and send it control commands.

This syntax gives you more flexibility to flush content based on regex's, either on the host, the URL or arbitrary headers.


Part Six: ESI, TTL's, Grace & Saint

varnish_explained_053

More acronyms! More!


varnish_explained_054

Edge Side Includes are a very powerful but tricky feature of Varnish. By default, Varnish will either cache an entire page, or not cache an entire page.

With ESI, Varnish can cache parts of a page. Funky, right?

In a way, this works very similar to PHP's include or require method or the old Server Side Includes in webservers.


varnish_explained_055

Take for instance a default layout for a news website: it'll contain a personal greeting at the top, some ads, some navigation and the main body.

These different "fragments" that usually build a page are what Varnish needs to be able to do partial caching.


varnish_explained_056

Instead of generating the content for each partial directly in the HTML/DOM of the page, you generate special esi:include tags instead.


varnish_explained_057

Eventually, your server-side generated HTML looks like this. It has several esi:include blocks, where it would normally have either include/require statements from PHP. But instead of having PHP do the combining of the page, those esi:include tags are going to be interpreted by Varnish.


varnish_explained_058

When Varnish processes a vcl_backend_response(), it can look inside the body and detect those esi:include tags.

For each of those ESI tags, it will -- on its own -- send a new request to the backend and request those URLs. The backend will in turn process them, reply them back to Varnish, and Varnish will build the page by glueing all the fragments together.

The biggest benefit is that each fragment can have its own caching policy: in the best case scenario, all those esi:include calls were already in the Varnish cache and it could assemble the page entirely from memory.

The magic of ESI is that the user just requested a single page, it had no idea that it would in turn fire of multiple small requests to the backend to assemble that page before getting it delivered.


varnish_explained_059

Now, by default, Varnish will look at the Cache-Control headers (or ETAG, Expires, ...) to determine how long it can cache an object. In the absolute best case scenario, the HTTP response headers determine the lifetime of each HTTP response.

But you can artificially overwrite that response by setting a new value in the beresp.ttl object.


varnish_explained_060

The TTL or cache lifetime can be modified even further by introducing the concept of Grace Mode.

You can let Varnish keep objects in its cache that have actually surpassed their lifetime. This is useful in case your backends are down; it's better to serve old content, than no content.

It's also of value when there are HTTP requests coming in for expired objects: Varnish can reply with a slightly outdated response, while asynchronously fetching a new version from the backend. Your user never needs to wait for that response (it got a slightly outdated response), while your webserver can generate the response.

This is absolutely a lifesaver in cache stampedes scenario's, where Varnish will only send a single request to the backend, even though 100+ users are requesting the same page.


Bonus: CLI tools

varnish_explained_061

Quick demo: some CLI tools.


varnish_explained_062

varnishhist shows a histogram of the requests currently being served by Varnish: the more to the left they are shown, the faster they have been served. These are generally more cache hits, as they're served the fastest.

The more items to the right, the slower each request is.

A pipe sign | is a cache hit, a hashtag # is a cache miss (can also be a cache miss on purpose, if the VCL code says return (pass);).

varnishhist is a good quick glance to see if Varnish is caching anything and if so, what the overal ratio is: lots of cache hits or misses. But beyond that, the tool probably won't tell you that much.


varnish_explained_063

varnishlog on the other hand is the real gem: it will show in great detail the request headers of a user and the response headers returned by Varnish. If you really need to debug Varnish, this'll be the tool that tells you the most.

There are filters you can apply to only log requests from a certain IP (ie: your source IP), only log requests matching a certain URL pattern, you can see which cookies are being stripped in the VCL and which aren't, etc.

The only downside: varnishlog can be a bit daunting at first with lots of output, it takes a while to grok the format.


varnish_explained_064

Another interesting one, especially when just getting started with Varnish, is varnishncsa: it takes the complicated varnishlog and shows the output just like the Apache or Nginx access logs.

It won't tell you many details (like which cookies get stripped, which VCL routines got triggered etc.), but it will tell you in a more readable format (or at least: a format you're more used to) which requests Varnish is serving.

You can also run varnishncsa as a daemon/service to log all requests made by Varnish in this format, a bit like the default behaviour in Apache or Nginx. For debugging purposes, this could be useful.


Part Seven: getting started with Varnish

varnish_explained_065

Let's get started.


varnish_explained_066

This is the really short version of getting started with Varnish;

  • Download my Varnish templates from Github
  • Install varnish, by default it'll run on a custom port (check /etc/sysconfig/varnish)
  • Configure your backend so that it points to your existing Apache/Nginx
  • Test your site on the custom port: domain.tld:8080
  • If everything still works, swap the ports: set your Apache/Nginx on port :8080, move Varnish to port :80 and change the backends in Varnish so that it sends its requests to port :8080.

After that, you're done: Varnish now serves all requests on port :80 and will proxy everything it can't serve to port :8080.

Chances are, it'll take longer to implement Varnish than just those couple of steps, but that's basically the gist of it. Run Varnish on an alternative port, so that you can test it safely without interfering with your site, and once it's tested and approved, swap out the ports and let it run on port :80.

A couple of tips, that might help you get started quicker;

As with everything: testing, testing & testing is what will make Varnish deployment a success!


varnish_explained_067

The end!

For any questions, leave a comment on this blog, poke me on Twitter, send me an e-mail, ...



Hi! My name is Mattias Geniar. I'm a Support Manager at Nucleus Hosting in Belgium, a general web geek, public speaker and podcaster. Currently working on DNS Spy. Follow me on Twitter as @mattiasgeniar.

I respect your privacy and you won't get spam. Ever.
Just a weekly newsletter about Linux and open source.

SysCast podcast

In the SysCast podcast I talk about Linux & open source projects, interview sysadmins or developers and discuss web-related technologies. A show by and for geeks!

cron.weekly newsletter

A weekly newsletter - delivered every Sunday - for Linux sysadmins and open source users. It helps keeps you informed about open source projects, Linux guides & tutorials and the latest news.

Share this post

Did you like this post? Will you help me share it on social media? Thanks!

Comments

Martin Wednesday, October 26, 2016 at 03:54

Excellent presentation.

After playing all the day with varnish i think it’s important to mention (maybe a bigger note) that varnish doesn’t support ssl, so you will end with a nginx in front.

Reply


Mattias Geniar Wednesday, October 26, 2016 at 11:55

Valid point, I’ll adjust the slides as well to make that more clear!

Reply


Q Sunday, October 30, 2016 at 17:27

Hey. Great post. This could very well be the best “intro to varnish” blog post

I’d suggest that BAN is in fact less desirable than a well implemented PURGE. It’s tricky but if you manage Vary headers well and make use of Surrogate Keys via libvmod-xkey vmod you get very simple and powerful cache purging

BAN is async which is not always what you want especially when the app itself is varnish-aware and proactively invalidates cache by issuing BAN/PURGE directly to varnish

And I’d also add that varnish has very sane defaults of out of the box. It’s desirable to never override the default vcl IMO

Reply


Leave a Reply

Your email address will not be published. Required fields are marked *

Inbound links