Varnish VCL Custom config

#vcl 4.0;

#Based on: #https://github.com/mattiasgeniar/varnish-4.0-configuration-#templates/blob/master/default.vcl

import std;

import directors;

#BACKEND_SERVERS**

#Called when VCL is loaded, before any requests pass through it.

#Typically used to initialize VMODs.

#BACKEND_DIRECTORS**

#Called when VCL is loaded, before any requests pass through it.

#Typically used to initialize VMODs.

sub vcl_init {

}

sub vcl_pass {

  return (pass);

}

#SET THE ALLOWED IP OF PURGE REQUESTS

###########################################################

acl purge {

  "localhost";

  "127.0.0.1";

  "Web.Server.IP";

}

#Called at the beginning of a request, after the complete request has been received and parsed.

#Its purpose is to decide whether or not to serve the request, how to do it, and, if applicable,

#which backend to use.

#also used to modify the request

#Normalize the header, remove the port (in case you're testing this on various TCP ports)

sub vcl_recv {  

  set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

#remove HTTPOXY CGI vulnerability

  unset req.http.proxy;

#Some generic cookie manipulation, useful for all templates that follow

#Remove the "has_js" cookie

  set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");

#Remove any Google Analytics based cookies

  set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");

  set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", "");

  set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", "");

  set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", "");

  set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");

  set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");

#Remove DoubleClick offensive cookies

  set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");

#Remove the Quant Capital cookies (added by some plugin, all __qca)

  set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");

#Remove the AddThis cookies

  set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");

#Remove a ";" prefix in the cookie if present

  set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");

#Are there cookies left with only spaces or that are empty?

  if (req.http.cookie ~ "^\s*$") {

    unset req.http.cookie;

  }

#Remove all cookies for static files

#A valid discussion could be held on this line: do you really need to cache static files that don't #cause load? Only if you have memory left.

#Sure, there's disk I/O, but chances are your OS will already have these files in their buffers ##(thus memory).

#Before you blindly enable this, have a read here: https://ma.ttias.be/stop-caching-static-files/

  if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {

    unset req.http.Cookie;

    return (hash);

  }

#remove extraneous host ports

  set req.http.host = regsub(req.http.Host, ":[0-9]+", "");

#set realIP by trimming CloudFlare IP which will be used for various checks

  set req.http.X-Actual-IP = regsub(req.http.X-Forwarded-For, "[, ].*$", ""); 

#Enable smart refreshing

  if (req.http.Cache-Control ~ "no-cache" && client.ip ~ purge) {

    set req.hash_always_miss = true;

  }

#Unset cloudflare cookies

#Remove has_js and CloudFlare/Google Analytics __* cookies.

  set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");

#Remove a ";" prefix, if present.

  set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");

#For Testing: If you want to test with Varnish passing (not caching) uncomment

#return( pass );

#FORWARD THE IP OF THE REQUEST

  if (req.restarts == 0) {

    if (req.http.x-forwarded-for) {

      set req.http.X-Forwarded-For =

      req.http.X-Forwarded-For + ", " + client.ip;

    } else {

    set req.http.X-Forwarded-For = client.ip;

    }

  }

  set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

#Remove the proxy header (see https://httpoxy.org/#mitigate-varnish)

  unset req.http.proxy;

#Normalize the query arguments

  set req.url = std.querysort(req.url);

#Non-RFC2616 or CONNECT which is weird.

  # Only deal with "normal" types

  if (req.method != "GET" &&

      req.method != "HEAD" &&

      req.method != "PUT" &&

      req.method != "POST" &&

      req.method != "TRACE" &&

      req.method != "OPTIONS" &&

      req.method != "PATCH" &&

      req.method != "DELETE") {

    /* Non-RFC2616 or CONNECT which is weird. */

    /*Why send the packet upstream, while the visitor is using a non-valid HTTP method? */

    return (synth(404, "Non-valid HTTP method!"));

  }

  # Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)

  if (req.http.Upgrade ~ "(?i)websocket") {

    return (pipe);

  }

  # Only cache GET or HEAD requests. This makes sure the POST requests are always passed.

  if (req.method != "GET" && req.method != "HEAD") {

    return (pass);

  }

  # Some generic URL manipulation, useful for all templates that follow

  # First remove URL parameters used to track effectiveness of online marketing campaigns

  if (req.url ~ "(\?|&)(utm_[a-z]+|gclid|cx|ie|cof|siteurl|fbclid)=") {

      set req.url = regsuball(req.url, "(utm_[a-z]+|gclid|cx|ie|cof|siteurl|fbclid)=[-_A-z0-9+()%.]+&?", "");

      set req.url = regsub(req.url, "[?|&]+$", "");

  }

#Not cacheable by default

  if (req.http.Authorization) {

    return (pass);

  }

#For static file access, strip all querystring parameters.

  if (req.url ~ "^[^?]*\.(bmp|bz2|css|doc|eot|flv|gif|gz|ico|jpe?g|js|less|mp[34]|otf|pdf|png|rar|rtf|swf|tar|tgz|ttf|txt|wav|webm|woff|xml|zip)(\?.*)?$") {

    unset req.http.Cookie;

    set req.url = querystring.remove(req.url);

    return (hash);

  }

#Whitelist query string parameters for WordPress.

  set req.url = querystring.filter_except(req.url,

    "sort" + querystring.filtersep() +

    "q" + querystring.filtersep() +

    "dom" + querystring.filtersep() +

    "dedupe_hl" + querystring.filtersep() +

    "filter" + querystring.filtersep() +

    "attachment" + querystring.filtersep() +

    "attachment_id" + querystring.filtersep() +

    "author" + querystring.filtersep() +

    "author_name" + querystring.filtersep() +

    "cat" + querystring.filtersep() +

    "calendar" + querystring.filtersep() +

    "category_name" + querystring.filtersep() +

    "comments_popup" + querystring.filtersep() +

    "cpage" + querystring.filtersep() +

    "day" + querystring.filtersep() +

    "dedupe_hl" + querystring.filtersep() +

    "dom" + querystring.filtersep() +

    "error" + querystring.filtersep() +

    "exact" + querystring.filtersep() +

    "exclude" + querystring.filtersep() +

    "feed" + querystring.filtersep() +

    "hour" + querystring.filtersep() +

    "m" + querystring.filtersep() +

    "minute" + querystring.filtersep() +

    "monthnum" + querystring.filtersep() +

    "more" + querystring.filtersep() +

    "name" + querystring.filtersep() +

    "order" + querystring.filtersep() +

    "orderby" + querystring.filtersep() +

    "p" + querystring.filtersep() +

    "page_id" + querystring.filtersep() +

    "page" + querystring.filtersep() +

    "paged" + querystring.filtersep() +

    "pagename" + querystring.filtersep() +

    "pb" + querystring.filtersep() +

    "post_type" + querystring.filtersep() +

    "posts" + querystring.filtersep() +

    "preview" + querystring.filtersep() +

    "q" + querystring.filtersep() +

    "robots" + querystring.filtersep() +

    "s" + querystring.filtersep() +

    "search" + querystring.filtersep() +

    "second" + querystring.filtersep() +

    "sentence" + querystring.filtersep() +

    "sort" + querystring.filtersep() +

    "static" + querystring.filtersep() +

    "subpost" + querystring.filtersep() +

    "subpost_id" + querystring.filtersep() +

    "taxonomy" + querystring.filtersep() +

    "tag" + querystring.filtersep() +

    "tb" + querystring.filtersep() +

    "tag_id" + querystring.filtersep() +

    "term" + querystring.filtersep() +

    "tb" + querystring.filtersep() +

    "url" + querystring.filtersep() +

    "w" + querystring.filtersep() +

    "withcomments" + querystring.filtersep() +

    "withoutcomments" + querystring.filtersep() +

    "year");

#Sort the querystring parameters, so different orders of the same produce a single cache #object.

  if (req.url ~ "\?") {

    set req.url = querystring.sort(req.url);

  }

#Keep WordPress cookies for preview

#drop all cookies.  

  if (req.http.cookie) {

    if (req.url ~ "preview") {

      return (pass);

    } else {

      unset req.http.cookie;

    }

  return (hash);

}

#The data on which the hashing will take place

#Called after vcl_recv to create a hash value for the request. This is used as a key

#to look up the object in Varnish.

sub vcl_hash {

  if (req.http.cookie) {

    hash_data(req.http.cookie);

  }

  return(lookup);

}

#fix flexible ssl css

  if (req.http.x-forwarded-proto) {

    hash_data(req.http.x-forwarded-proto);

  }

  if (req.http.host) {

    hash_data(req.http.host);

  } else {

    hash_data(server.ip);

    set req.http.ccsuri = regsub(req.url, "\?(.*)", "");

    hash_data(req.http.ccsuri);

  }

  return (hash);

}

#Called upon entering pass mode. In this mode, the request is passed on to the backend, and #the

#backend's response is passed on to the client, but is not entered into the cache. Subsequent

#requests submitted over the same client connection are handled normally.

#The data on which the hashing will take place

#Called when a cache lookup is successful.

#A pure unadultered hit, deliver it

sub vcl_hit {

  if (obj.ttl >= 0s) {

    return (deliver);

  }

  if (req.request == "PURGE") {

    purge;

    error 200 "Purged.";

    }

  return (deliver);

#https://www.varnish-cache.org/docs/trunk/users-guide/vcl-grace.html

#When several clients are requesting the same page Varnish will send one request to the #backend and #place the others on hold while fetching one copy from the backend. In some #products this is called #request coalescing and Varnish does this automatically.

#If you are serving thousands of hits per second the queue of waiting requests can get huge. #There #are two potential problems – one is a thundering herd problem – suddenly releasing a #thousand #threads to serve content might send the load sky high. Secondly – nobody likes to #wait. To deal #with this we can instruct Varnish to keep the objects in cache beyond their TTL #and to serve the #waiting requests somewhat stale content.

#Object is in grace, deliver it

#Automatically triggers a background fetch

  if (obj.ttl + obj.grace > 0s) {

    return (deliver);

  }

#fetch & deliver once we get the result

    return (fetch);

  }

  if (!std.healthy(req.backend_hint) && (obj.ttl + obj.grace > 0s)) {

  return (deliver);

  } else {

    return (miss);

  }

#We have no fresh fish. Lets look at the stale ones.

#Backend is healthy. Limit age to 10s.

  set req.http.grace = "normal(limited)";

#No candidate for grace. Fetch a fresh object.

#backend is sick – use full grace

#no graced object.

  if (std.healthy(req.backend_hint)) {

    if (obj.ttl + 10s > 0s) {

      return (deliver);

    } else {

      return (fetch);

    }

  } else {

      if (obj.ttl + obj.grace > 0s) {

      set req.http.grace = "full";

      return (deliver);

    } else {

      return (fetch);

    }

  }

#fetch & deliver once we get the result

#Dead code, keep as a safeguard

  return (fetch);

}

#Called after a cache lookup if the requested document was not found in the cache. Its purpose

#is to decide whether or not to attempt to retrieve the document from the backend, and which

#backend to use.

sub vcl_miss {

  return (fetch);

}

#Handle the HTTP request coming from our backend

#Called after the response headers has been successfully retrieved from the backend.

#Pause ESI request and remove Surrogate-Control header

sub vcl_backend_response {

  if (beresp.http.Surrogate-Control ~ "ESI/1.0") {

    unset beresp.http.Surrogate-Control;

    set beresp.do_esi = true;

  }

  if (!(bereq.url ~ "wp-(login|admin)|cart|my-account|wc-api|resetpass") &&

    !bereq.http.cookie ~ "wordpress_logged_in|woocommerce_items_in_cart|resetpass" && !beresp.status == 302) {

    unset beresp.http.set-cookie;

    set beresp.ttl = 1w;

    set beresp.grace = 1d;

  }

#Enable cache for all static files

#The same argument as the static caches from above: monitor your cache size, if you get data nuked #out of it, consider giving up the static file cache.

#Before you blindly enable this, have a read here: https://ma.ttias.be/stop-caching-static-files/

  if (bereq.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {

    unset beresp.http.set-cookie;

  }

#Large static files are delivered directly to the end-user without

#waiting for Varnish to fully read the file first.

#Varnish 4 fully supports Streaming, so use streaming here to avoid locking.

  if (bereq.url ~ "^[^?]*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") {

    unset beresp.http.set-cookie;

    set beresp.do_stream = true;  

    return (hash);

  }

#Don't cache 50x responses

  if (beresp.status == 500 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) {

    return (abandon);

  }

#Sometimes, a 301 or 302 redirect formed via Apache's mod_rewrite can mess with the HTTP #port that #is being passed along.

#This often happens with simple rewrite rules in a scenario where Varnish runs on :80 and #Apache on #:8080 on the same box.

#A redirect can then often redirect the end-user to a URL on :8080, where it should be :80.

#This may need finetuning on your setup.

#To prevent accidental replace, we only filter the 301/302 redirects for now.

  if (beresp.status == 301 || beresp.status == 302) {

    set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", "");

  }

#Set 2min cache if unset for static files

  if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {

    set beresp.ttl = 120s;    

    set beresp.uncacheable = true;

    return (deliver);

  }

#Allow stale content, in case the backend goes down.

#make Varnish keep all objects for 6 hours beyond their TTL

  set beresp.grace = 6h;

  return (deliver);

}

#The routine when we deliver the HTTP request to the user

#Last chance to modify headers that are sent to the client

#Called before a cached object is delivered to the client.

#Add debug header to see if it's a HIT/MISS and the number of hits, disable when not needed

sub vcl_deliver {

  if (obj.hits > 0) { 

    set resp.http.X-Cache = "HIT";

  } else {

    set resp.http.X-Cache = "MISS";

  return (deliver);

}

#Please note that obj.hits behaviour changed in 4.0, now it counts per objecthead, not per #object

#and obj.hits may not be reset in some cases where bans are in use. See bug 1492 for #details.

#So take hits with a grain of salt

  set resp.http.X-Cache-Hits = obj.hits;

  if (req.http.sticky) {

    if(!resp.http.Set-Cookie) {

      set resp.http.Set-Cookie = "";

    }

    set resp.http.Set-Cookie = "ccsvs=ccs" + req.http.sticky + ";   Expires=" + (now + 10d) + ";" + resp.http.Set-Cookie;

  }

#Remove some headers: PHP version

  unset resp.http.X-Powered-By;

#Remove some headers: Apache version & OS

  unset resp.http.Server;

  unset resp.http.X-Drupal-Cache;

  unset resp.http.X-Varnish;

  unset resp.http.Via;

  unset resp.http.Link;

  unset resp.http.X-Generator;

  return (deliver);

}

#Only handle actual PURGE HTTP methods, everything else is discarded

#restart request

sub vcl_purge {

  if (req.method != "PURGE") {

    set req.http.X-Purge = "Yes";

    return(restart);

  }

}

#You may need to add other locations like membership sites here, 302 is necessary if you use #redirect to cart

###########################################################

  if (!(req.url ~ "wp-(login|admin)|wc-api|resetpass|cart|checkout|my-account|\?wc-ajax=get_refreshed_fragments") && !req.http.cookie ~ "wordpress_logged_in|resetpass" && !beresp.status == 302) {

    unset beresp.http.set-cookie;

    set beresp.ttl = 1w;

    set beresp.grace =3d;

  }

  if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { 

    set beresp.ttl = 120 s;

    return (hit_for_pass);

  }

  return (deliver);

}

#We use this special error status 8201 for clear cache messages

#We use this special error status 720 to force redirects with 301 (permanent) redirects

#To use this, call the following from anywhere in vcl_recv: return (synth(720, #"http://host/new.html"));

#And we use error status 721 to force redirects with a 302 (temporary) redirect

#To use this, call the following from anywhere in vcl_recv: return (synth(720, #"http://host/new.html"));

sub vcl_synth {

  if (resp.status == 720) {

    set resp.http.Location = resp.reason;

    set resp.status = 301;

    return (deliver);

  } elseif (resp.status == 721) {

    set resp.http.Location = resp.reason;

    set resp.status = 302;

    return (deliver);

  }

#Called when VCL is discarded only after all requests have exited the VCL.

#Typically used to clean up VMODs.

sub vcl_fini {

  return (ok);

}

#Called upon entering pipe mode.

#In this mode, the request is passed on to the backend, and any further data from both the #client

#and backend is passed on unaltered until either end closes the connection. Basically, Varnish #will

#degrade into a simple TCP proxy, shuffling bytes back and forth. For a connection in pipe #mode,

#no other VCL subroutine will ever get called after vcl_pipe.

#Note that only the first request to the backend will have

#X-Forwarded-For set.  If you use X-Forwarded-For and want to

#have it set for all requests, make sure to have:

#here.  It is not set by default as it might break some broken web

#applications, like IIS with NTLM authentication.

#Implementing websocket support #(https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-#websockets.html)

sub vcl_pipe {

  set bereq.http.Connection = "Close";

  set bereq.http.connection = "close";

  if (req.http.upgrade) {

    set bereq.http.upgrade = req.http.upgrade;

  }

  return (pipe);

}

Leave a Reply

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