March 8, 2017
4 min read time

Varnish Configuration Language: VCL snippets

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):

  1. “Do I know exactly what I want to achieve? Any corner case?”
    Most likely the answer is “NO”.

  2. “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”.

  3.  “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

  1. ACL and Purge
    # 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”));
        }
      } 
    }
    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.
  2. Authentication
    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"));
      }
    }
    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.
  3. Stale-while-revalidate
    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);
        }
      }
    }
    To get a more in-depth overview of how stale-while-revalidate works, here is a very good blog post on the subject.
  4. Stale-if-error
    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;
    }
    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.
  5. Redirect to a permanent or temporary location
    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);
       }
    }
    More on how to redirect to a new location in Varnish here.

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. 

LEARN MORE ABOUT PROFESSIONAL SERVICES