The Varnish Configuration Language (VCL) is a small domain-specific language designed to be used to define request handling within Varnish. It is extremely flexible and allows you to let Varnish do exactly whatever you want or need it to do.
As you probably already know, defining any sort of logic in VCL requires some lines of code to be written, which can go from a few lines of VCL or, in more complex scenarios, it might scale up to thousands of lines of VCL.
The following scenarios are typical of what usually happens whenever a new piece of VCL needs to be developed and put in place in an already existing VCL file (from the user perspective):
-
“Do I know exactly what I want to achieve? Any corner case?”
Most likely the answer is “NO”. -
“I’m not sure how to do this; let me Google it a little bit just to check if there are good examples somewhere on the web.”
Usually there are tons of VCL examples that Google (or any other search engine) can provide, but only a few of them are actually “good examples”. -
“Well, I’ll just copy and paste this first result and put it in production to check how it behaves and eventually modify it when/if the time comes.”
Most likely you won’t get the expected outcome. Instead you may get a less performant and more troubled Varnish installation. We always suggest that you not copy-paste VCL snippets without actually fully understanding what they are supposed to do and without testing them properly.
I have collected five different VCL snippets for which the logic has been tested, meaning that at least in these cases, you can actually copy-paste any of the following VCL snippets and use them in your Varnish installations
- ACL and Purge
We define an ACL (access control list,) which in this scenario will include the clients’ IPs that are allowed to purge content from your cache. In “vcl_recv” we check if the client sends a purge request and whether its IP is part of the “local” ACL, then some content will be purged from cache otherwise it will get an “access denied” response.# Who is allowed to purge acl local { “Localhost”; “192.168.1.0”/24; /* and everyone on the local network */ ! “192.168.1.23”; /* except for the dialin router */ } sub vcl_recv { if (req.method == “PURGE”) { if (client.ip ~ local) { return(purge); } else { return(synth(403, “Access denied”)); } } }
- Authentication
For any incoming request, we first unset, if present, the authstatus header. Afterwards, if the client request includes a signature header, we need to make sure that such a header matches the sig-verf header(we use the digest VMOD for this: https://github.com/varnish/libvmod-digest) If the authentication header and the sig-verf header match, then the user can be authenticated.sub vcl_recv { if (req.http.authstatus) { unset req.http.authstatus; } if (req.http.signature) { set req.http.sig-verf = digest.hmac_sha256("key", req.http.host); if (req.http.sig-verf == req.http.signature) { set req.http.authstatus = "ok"; } } if (req.http.authstatus == "ok") { # implement your logic for authenticated users return(synth(200, "ok")); } else { # implement your logic for unauthenticated users return(synth(401, "not ok")); } }
- Stale-while-revalidate
To get a more in-depth overview of how stale-while-revalidate works, here is a very good blog post on the subject.sub vcl_hit { if (obj.ttl >= 0s) { # normal hit return (deliver); } # We have no fresh fish. Lets look at the stale ones. if (std.healthy(req.backend_hint)) { # Backend is healthy. Limit age to 10s. if (obj.ttl + 10s > 0s) { set req.http.grace = "normal(limited)"; return (deliver); } else { # No candidate for grace. Fetch a fresh object. return(fetch); } } else { # backend is sick - use full grace if (obj.ttl + obj.grace > 0s) { set req.http.grace = "full"; return (deliver); } else { # no graced object. return (fetch); } } }
- Stale-if-error
Calling “try_stale_if_error” will evaluate whether the object still has grace time left, and whether the request has been restarted or not. If it has not been restarted, it will simply set the req.http.sie-enabled header, and continue to fetch. If during the fetch it encounters an error, instead of returning the error to the client, it will check for the req.http.sie-enabled header, and if set go to vcl_synth, which in turn forces a restart returning to the top. Now if the request has already been restarted, and the object has grace time left, it will then deliver the “grace” object.sub try_stale_if_error { if (obj.ttl < 0s && obj.ttl + obj.grace > 0s) { if (req.restarts == 0) { set req.http.sie-enabled = true; return (fetch); } else { set req.http.sie-abandon = true; return (deliver); } } } sub vcl_backend_fetch { if (bereq.http.sie-abandon) { return (abandon); } } sub vcl_backend_response { if (beresp.status > 400 && bereq.http.sie-enabled) { return (abandon); } } sub vcl_backend_error { if (bereq.http.sie-enabled) { return (abandon); } } sub vcl_synth { if (resp.status == 503 && req.http.sie-enabled) { unset req.http.sie-enabled; return (restart); } } sub vcl_hit { call try_stale_if_error; }
- Redirect to a permanent or temporary location
More on how to redirect to a new location in Varnish here.sub vcl_recv { unset req.http.location; if (req.url ~ "/permanent") { set req.http.location = "https://new.example.com" + req.url; return(synth(301)); } if (req.url ~ "/temporary") { set req.http.location = "https://temporary.example.com" + req.url; return(synth(302)); } } sub vcl_synth { # Permanent redirect if (resp.status == 301) { set resp.http.Location = req.http.location; return (deliver); } # Temporary redirect if (resp.status == 302) { set resp.http.Location = req.http.location; return (deliver); } }
If you need help with your VCL, or need some expertise in creating and implementing something more advanced, get in touch to discuss our Varnish Professional Services packages.