Varnish Web Accelerator
Getting going - simplest possible setup
part1_basic.cfm
<CFHEADER NAME="Cache-Control" VALUE="s-maxage=600">
<cfoutput>
<html>
<body>
<strong>Generated by server:</strong> #dateformat(now(),'ddd, mmm d yyyy')# #timeformat(now(),'HH:mm:ss')#<br>
<strong>Loaded by browser:</strong> <script type="text/javascript">document.write(new Date());</script>
</body>
</html>
</cfoutput>The default varnish installation serves cached content from port 6081 proxied from 127.0.0.1:80.
that is important - the default varnish config is appended to yours
Cookies (i.e. sessions) should be disabled, as by default varnish will invalidate caches if cookies are present.
Setting browser cache separate to varnish cache - you can control varnish cache but not client
Static assets
/etc/varnish/default.vcl
sub vcl_recv {
if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset req.http.Accept-Encoding;
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
unset req.http.Cookie;
return(lookup);
}
}
virtual.conf
<VirtualHost *:80>
...
# Set up caching on media files for 1 year (forever?)
<FilesMatch "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$">
ExpiresDefault A29030400
Header append Cache-Control "public"
</FilesMatch>
</VirtualHost>
Tips
Leave the default backend (127.0.0.1) in place.
~ is a regex operator for strings
set and unset update and remove a property
req.http properties are request headers
this configuration leaves control of cache timeouts with Apache, but you can force a cache timeout in varnish with a line like "set beresp.ttl = 48h;"
return(lookup) returns control to varnish, requesting a cache lookup if possible
if there is not return in your config, the default varnish config is executed
Flush Varnish cache from backend
/etc/varnish/default.vcl
// IPs/domains that can access the purge url
acl purge {
"localhost";
"117.53.174.178";
"117.53.174.179";
"203.26.11.39";
}
sub vcl_recv {
// Purge everything url - this isn't the squid way, but works
if (req.url ~ "^/varnishpurge") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
if (req.url == "/varnishpurge") {
ban("req.http.host == " + req.http.host + " && req.url ~ ^/");
error 841 "Purged site.";
}
else {
ban("req.http.host == " + req.http.host + " && req.url ~ ^" + regsub( req.url, "^/varnishpurge(.*)$", "\1" ) + "$" );
error 842 "Purged page.";
}
}
}
it's a good idea to use an ACL to restrict access to the sensitive functionality like flushing
notice that for ACLs, the ~ operator is an 'in' check
this acl sets up a URL that will trigger a flush '/varnishpurge'
/varnishpurge by itself purges every page on the domain
/varnishpurge/url/you/want/to/purge purges /url/you/want/to/purge
'ban' is for varnish 3 what 'purge' was for 2
in 3 you can do "string" + myvar, in 2 it was just "string" myvar (implicit concatenation)
notice the custom error numbers
bans are stored in memory, and every page request is checked against every ban - there are performance implications
Invalidate cache from client side
By IP
/etc/varnish/default.vcl
// IPs/domains that bypass cache
acl bypass {
"1.2.3.4";
}
sub vcl_recv {
if (client.ip ~ bypass) {
return(pass);
}
}
return(pass) passes the request to the backend
By Cookie
/etc/varnish/default.vcl
sub vcl_recv {
if (req.http.Cookie ~ "LOGGED-IN=1") {
return(pass)
}
}
bypasses cache if there is a LOGGED-IN cookie with value 1
By Header
/etc/varnish/default.vcl
sub vcl_recv {
if (req.http.X-Requested-With == "XMLHttpRequest") {
return(pass)
}
}
ajax requests bypass cache
Caching strategies; you can set, and flush now for real world scenarios
Bomb proof static delivery: fullasagoog.com
/etc/varnish/default.vcl
sub vcl_recv {
set req.grace = 300s;
}
sub vcl_fetch {
if (beresp.status == 500) {
set beresp.saintmode = 20s;
if (req.request != "POST") {
return(restart);
} else {
error 500 "Failed";
}
}
set beresp.grace = 300s;
set beresp.ttl = 5s;
}
|
Members vs The great unwashed: adnews
flat proxy for anonymous users
live site for logged in users; show VCL looking for loggedin cookies
/etc/varnish/default.vcl
sub vcl_hash {
### these 2 entries are the default ones used for vcl. Below we add our own.
set req.hash += req.http.host;
set req.hash += req.url;
set req.http.X-Varnish-Hashed-By = req.http.host req.url;
if (req.http.Cookie ~ "LOGGED-IN=1") {
set req.hash += regsub( req.http.Cookie, "^.*?LOGGED-IN=1" );
set req.http.X-Varnish-Hashed-By = req.http.X-Varnish-Hashed-By regsub( req.http.Cookie, "^.*?LOGGED-IN=1" );
}
return(hash);
}
sub vcl_recv {
// Let any non "GET" / "HEAD" right through
if (req.request != "GET" && req.request != "HEAD"){
return(pass);
}
// strip cookies for static files
if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset req.http.Accept-Encoding;
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
unset req.http.Cookie;
return(lookup);
}
// strip cookies for everything except specific pages
if ( !(req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout") ) {
// Clean up accept encoding values
# Handle compression correctly. Varnish treats headers literally, not
# semantically. So it is very well possible that there are cache misses
# because the headers sent by different browsers aren't the same.
# @see: http://varnish.projects.linpro.no/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# if the browser supports it, we'll use gzip
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
# next, try deflate if it is supported
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm. Probably junk, remove it
unset req.http.Accept-Encoding;
}
}
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
// If the user is logged in, let session cookies through (Note: these values should not be getting included in the cache hash)
if (req.http.Cookie ~ "FC-LOGGED-IN=1") {
set req.http.Cookie = ";" req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(CFID|CFTOKEN|JSESSIONID|FC-LOGGED-IN|FC-ROLES)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
}
return(lookup);
}
else {
return(pass);
}
}
sub vcl_fetch {
// strip cookies for static files
if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset beresp.http.set-cookie;
return(deliver);
}
// strip cookies for everything except specific pages
if ( req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout" ) {
set beresp.http.X-Cacheable = "NO:Login or logout page";
return(pass);
}
else {
unset beresp.http.set-cookie;
}
// varnish determined the object was not cacheable
if (!beresp.cacheable){
set beresp.http.X-Cacheable = "NO:Not Cacheable";
}
elsif (beresp.http.Cache-Control ~ "private"){
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
return(pass);
}
else {
// Keep stale content for a little while, to serve while varnish gets a fresh copy
set beresp.grace = 15s;
set beresp.http.X-Cacheable = "YES";
}
return(deliver);
}
User interactivity: adnews
form posts are not proxied (only GET); comment forms; show VCL ignoring POST requests
/etc/varnish/default.vcl
sub vcl_hash {
### these 2 entries are the default ones used for vcl. Below we add our own.
set req.hash += req.http.host;
set req.hash += req.url;
set req.http.X-Varnish-Hashed-By = req.http.host req.url;
if (req.http.Cookie ~ "LOGGED-IN=1") {
set req.hash += regsub( req.http.Cookie, "^.*?LOGGED-IN=1" );
set req.http.X-Varnish-Hashed-By = req.http.X-Varnish-Hashed-By regsub( req.http.Cookie, "^.*?LOGGED-IN=1" );
}
return(hash);
}
sub vcl_recv {
// Let any non "GET" / "HEAD" right through
if (req.request != "GET" && req.request != "HEAD"){
return(pass);
}
// strip cookies for static files
if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset req.http.Accept-Encoding;
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
unset req.http.Cookie;
return(lookup);
}
// strip cookies for everything except specific pages
if ( !(req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout") ) {
// Clean up accept encoding values
# Handle compression correctly. Varnish treats headers literally, not
# semantically. So it is very well possible that there are cache misses
# because the headers sent by different browsers aren't the same.
# @see: http://varnish.projects.linpro.no/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# if the browser supports it, we'll use gzip
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
# next, try deflate if it is supported
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm. Probably junk, remove it
unset req.http.Accept-Encoding;
}
}
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
// If the user is logged in, let session cookies through (Note: these values should not be getting included in the cache hash)
if (req.http.Cookie ~ "FC-LOGGED-IN=1") {
set req.http.Cookie = ";" req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(CFID|CFTOKEN|JSESSIONID|FC-LOGGED-IN)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
}
return(lookup);
}
else {
return(pass);
}
}
sub vcl_fetch {
// strip cookies for static files
if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset beresp.http.set-cookie;
return(deliver);
}
// Let any non "GET" / "HEAD" right through
if (req.request != "GET" && req.request != "HEAD"){
set beresp.http.X-Cacheable = "NO:Not GET or HEAD";
return(pass);
}
// strip cookies for everything except specific pages
elsif ( req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout" ) {
set beresp.http.X-Cacheable = "NO:Login or logout page";
return(pass);
}
else {
unset beresp.http.set-cookie;
}
// varnish determined the object was not cacheable
if (!beresp.cacheable){
set beresp.http.X-Cacheable = "NO:Not Cacheable";
}
elsif (beresp.http.Cache-Control ~ "private"){
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
return(pass);
}
else {
// Keep stale content for a little while, to serve while varnish gets a fresh copy
set beresp.grace = 15s;
set beresp.http.X-Cacheable = "YES";
}
return(deliver);
}
We use the following strategy to have user specific content on a site:
|
Personalized pods: adnews, newspaper works
Ajax personalized content after proxied page; show VCL ignoring Ajax requests
sub vcl_recv {
// strip cookies for static files
if (req.url ~ "^/favicon" || req.url ~ "^/cache" || req.url ~ "^/css" || req.url ~ "^/js" || req.url ~ "^/wsimages" || req.url ~ "^/base" || req.url ~ "^/webtop/cffp" || req.url ~ "^/webtop/icons"){
//if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset req.http.Accept-Encoding;
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
unset req.http.Cookie;
return(lookup);
}
// strip cookies for everything except specific pages
if ( !(req.url ~ "profile" || req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout") ) {
// Clean up accept encoding values
# Handle compression correctly. Varnish treats headers literally, not
# semantically. So it is very well possible that there are cache misses
# because the headers sent by different browsers aren't the same.
# @see: http://varnish.projects.linpro.no/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# if the browser supports it, we'll use gzip
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
# next, try deflate if it is supported
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm. Probably junk, remove it
unset req.http.Accept-Encoding;
}
}
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
return(lookup);
}
else {
return(pass);
}
}
sub vcl_fetch {
// strip cookies for static files
if (req.url ~ "^/favicon" || req.url ~ "^/cache" || req.url ~ "^/css" || req.url ~ "^/js" || req.url ~ "^/wsimages" || req.url ~ "^/base" || req.url ~ "^/webtop/cffp" || req.url ~ "^/webtop/icons"){
//if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
unset beresp.http.set-cookie;
return(deliver);
}
// Let any non "GET" / "HEAD" right through
if (req.request != "GET" && req.request != "HEAD"){
set beresp.http.X-Cacheable = "NO:Not GET or HEAD";
return(pass);
}
// strip cookies for everything except specific pages
elsif ( req.url ~ "profile" || req.url ~ "Login" || req.url ~ "login" || req.url ~ "logout" ) {
set beresp.http.X-Cacheable = "NO:Login or logout page";
return(pass);
}
else {
unset beresp.http.set-cookie;
}
// varnish determined the object was not cacheable
if (!beresp.cacheable){
set beresp.http.X-Cacheable = "NO:Not Cacheable";
}
elsif (beresp.http.Cache-Control ~ "private"){
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
return(pass);
}
else {
// Keep stale content for a little while, to serve while varnish gets a fresh copy
set beresp.grace = 15s;
set beresp.http.X-Cacheable = "YES";
}
return(deliver);
}
Tips
Normalising user agent and compression headers
Varnish can include the user-agent and accept-encoding headers in cache hash, so normalising them is a good idea:
/etc/varnish/default.vcl
sub vcl_recv {
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# if the browser supports it, we'll use gzip
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
# next, try deflate if it is supported
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm. Probably junk, remove it
unset req.http.Accept-Encoding;
}
}
// Remove user agent
if (req.http.User-Agent) {
set req.http.User-Agent = "";
}
}
If your website serves different HTML to mobile users, you might use code like this:
if (req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" || req.http.User-Agent ~ "Symbian" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson" || req.http.User-Agent ~ "^Nokia" || req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG" || req.http.User-Agent ~ " webOS") {
set req.http.User-Agent = "mobile";
}
else {
set req.http.User-Agent = "desktop";
}
Custom cache hash
This VCL adds a cookie value (ROLES) to the cache key. req.http.X-Varnish-Hashed-By is helpful for debugging, as you can inspect the request header to find out what the cache key is.
/etc/varnish/default.vcl
sub vcl_hash {
### these 2 entries are the default ones used for vcl. Below we add our own.
set req.hash += req.http.host;
set req.hash += req.url;
set req.http.X-Varnish-Hashed-By = req.http.host req.url;
if (req.http.Cookie ~ "ROLES=1") {
set req.hash += regsub( req.http.Cookie, "^.*?ROLES=([^;]*);*.*$", "\1" );
set req.http.X-Varnish-Hashed-By = req.http.X-Varnish-Hashed-By regsub( req.http.Cookie, "^.*?ROLES=([^;]*);*.*$", "\1" );
}
return(hash);
}
Pass along original IP address as a custom header
/etc/varnish/default.vcl
sub vcl_recv {
// Pass along the IP for uncached pages
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;
}
}
Refining your caching strategy.
Varnish tooling
showcase varnishhist
plus other command line utils