Using Varnish (v4) as a reverse proxy cache with Rails

Setting up Varnish

You can install varnish by following the official installation guide (this one is for Ubuntu, but you can find a official guide that supports your OS) at: https://www.varnish-cache.org/installation/ubuntu

Make sure to edit /etc/default/varnish and change DEAMON_OPTS so that varnish listens on port 80

/etc/default/varnish

DAEMON_OPTS="-a :80 \  
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -S /etc/varnish/secret \
             -s malloc,256m"

Caching only selected requests (white listing what to be cached)

The following code will ensure that Varnish will only cache whatever that matches the filters define. Everything else will be passed to the backend.

/etc/varnish/default.vcl

sub vcl_recv {  
    if (req.url ~ "^/api/method1/" ||
        req.url ~ "^/api/method2.json"
    ) {
         return(hash);
    }

    # pass everything to the backend by default
    return(pass);
}  

Cache-Control HTTP Headers

You have to set the proper cache-control HTTP headers for varnish to work properly. For example if a certain action is to be cached by varnish. You can do this inside the rails controller action.

expires_in 24.hours, public:true  

This will set the appropriate cache headers.

Purging

For manually asking varnish to purge a URL you have to send a HTTP PURGE request to same URL. Also you need to make sure your varnish vcl file have whitelisted the host making the purging requests.

Ex:

/etc/varnish/default.vcl

acl purgers {  
    "localhost";
    "xxx.xxx.xxx.xxx";
}

sub vcl_recv {  
    # Happens before we check if we have this in cache already.
    #
    # Typically you clean up the request here, removing cookies you don't need,
    # rewriting the request, etc.

    if (req.method == "PURGE") {
        if (client.ip ~ purgers) {
           return(purge);
        } else {
           return(synth(403, "Access denied."));
        }
      } 
}

Also you can see some example code on how to do this in ruby under:

https://github.com/robmiller/varnisher/blob/master/lib/varnisher/purger.rb

Other Varnish Trickery

Varnish can modify http headers and a lot more on the go. So for example if you want to remove cookie headers from requests before they are looked up by varnish you can do so.

Ex:

/etc/varnish/default.vcl

sub vcl_recv {  
    if (req.url ~ "^/api/method1/" ||
        req.url ~ "^/api/method2.json"
    ) {
        unset req.http.Cache-Control;
        unset req.http.Max-Age;
        unset req.http.Pragma;
         unset req.http.Cookie;
         return(hash);
    }

    # pass everything to the backend by default
    return(pass);
} 

Notes

If you find that examples you found online for varnish configs are not working as expected. Make sure they are compatible with the version of Varnish you are using. Refer to the upgrading guide linked bellow for more info on how to modify whatever examples you find to the latest version of varnish (v4).

Interesting gems

Varnisher: https://github.com/robmiller/varnisher
Lacquer: https://github.com/russ/lacquer

Documentation

https://www.varnish-cache.org/docs/4.0/users-guide
https://www.varnish-cache.org/docs/trunk/whats-new/upgrading.html

Example VCL files

varnish.vcl

staging
/etc/varnish/default.vcl

#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and http://varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend default {  
    .host = "xxx.xxx.xxx.xxx";
    .port = "80";
}

acl purgers {  
    "xxx.xxx.xxx.xxx";
    "xxx.xxx.xxx.xxx";
}

sub vcl_recv {  
    # Happens before we check if we have this in cache already.
    #
    # Typically you clean up the request here, removing cookies you don't need,
    # rewriting the request, etc.

    if (req.method == "PURGE") {
        if (client.ip ~ purgers) {
           return(purge);
        } else {
           return(synth(403, "Access denied."));
        }
      }

    if (req.url ~ "^/api/method1/" ||
        req.url ~ "^/api/method2.json"
    ) {
#        unset req.http.Cache-Control;
#        unset req.http.Max-Age;
#        unset req.http.Pragma;
#        unset req.http.Cookie;
     return(hash);
    }

    # pass everything to the backend by default
    return(pass);
}

sub vcl_backend_response {  
    # Happens after we have read the response headers from the backend.
    #
    # Here you clean the response headers, removing silly Set-Cookie headers
    # and other mistakes your backend does.

     if (bereq.url ~ "^/api/method1/" ||
        bereq.url ~ "^/api/method2.json"
    ) {
 #       unset beresp.http.Cache-Control;
 #       unset beresp.http.Max-Age;
 #       unset beresp.http.Pragma;
 #       unset beresp.http.Cookie;
        return(deliver);
    }
}

sub vcl_deliver {  
    # Happens when we have all the pieces we need, and are about to send the
    # response to the client.
    #
    # You can do accounting or modifying the final object here.
}

development

/etc/varnish/default.vcl

#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and http://varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend default {  
    .host = "localhost";
    .port = "8080";
}

acl local {  
    "localhost";
}

sub vcl_recv {  
    # Happens before we check if we have this in cache already.
    #
    # Typically you clean up the request here, removing cookies you don't need,
    # rewriting the request, etc.

    if (req.method == "PURGE") {
        if (client.ip ~ local) {
           return(purge);
        } else {
           return(synth(403, "Access denied."));
        }
      }

    if (req.url ~ "^/api/method1/" ||
        req.url ~ "^/api/method2"
    ) {
        unset req.http.Cache-Control;
        unset req.http.Max-Age;
        unset req.http.Pragma;
        unset req.http.Cookie;
        return(hash);
    }

    # pass everything to the backend by default
    return(pass);
}

sub vcl_backend_response {  
    # Happens after we have read the response headers from the backend.
    #
    # Here you clean the response headers, removing silly Set-Cookie headers
    # and other mistakes your backend does.
}

sub vcl_deliver {  
    # Happens when we have all the pieces we need, and are about to send the
    # response to the client.
    #
    # You can do accounting or modifying the final object here.
}