I have a Ruby on Rails application fronted by varnish+nginx. As most of the sites content is static unless you are a logged in user, I want to cache the site heavily with varnish when a user is logged out but only to cache static assets when they are logged in.
When a user is logged in they will have the cookie 'user_credentials' present in their Cookie: header, in addition I need to skip caching on /login and /sessions in order that a user can get their 'user_credentials' cookie in the first place.
Rails by default does not set a cache friendly Cache-control header, but my application sets a "public,s-max-age=60" header when a user is not logged in. Nginx is set to return 'far future' expires headers for all static assets.
The configuration I have at the moment is totally bypassing the cache for everything when logged in, including static assets — and is returning cache MISS for everything when logged out. I've spent hours going around in circles and here is my current default.vcl
director rails_director round-robin {
{
.backend = {
.host = "xxx.xxx.xxx.xxx";
.port = "http";
.probe = {
.url = "/lbcheck/lbuptest";
.timeout = 0.3 s;
.window = 8;
.threshold = 3;
}
}
}
}
sub vcl_recv {
if (req.url ~ "^/login") {
pipe;
}
if (req.url ~ "^/sessions") {
pipe;
}
# The regex used here matches the standard rails cache buster urls
# e.g. /images/an-image.png?1234567
if (req.url ~ "\.(css|js|jpg|jpeg|gif|ico|png)\??\d*$") {
unset req.http.cookie;
lookup;
} else {
if (req.http.cookie ~ "user_credentials") {
pipe;
}
}
# Only cache GET and HEAD requests
if (req.request != "GET" && req.request != "HEAD") {
pipe;
}
}
sub vcl_fetch {
if (req.url ~ "^/login") {
pass;
}
if (req.url ~ "^/sessions") {
pass;
}
if (req.http.cookie ~ "user_credentials") {
pass;
} else {
unset req.http.Set-Cookie;
}
# cache CSS and JS files
if (req.url ~ "\.(css|js|jpg|jpeg|gif|ico|png)\??\d*$") {
unset req.http.Set-Cookie;
}
if (obj.status >=400 && obj.status <500) {
error 404 "File not found";
}
if (obj.status >=500 && obj.status <600) {
error 503 "File is Temporarily Unavailable";
}
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
Ok, in the end I managed to solve this using the following vcl file. Note that I added a couple of extra bits to allow cache expiration grace when the backend has died.
It seems my main failure was using
unset req.http.Set-Cookie;
when I should have been usingunset obj.http.Set-Cookie;
in thevcl_fetch
section. (obj
in vcl_fetch andreq
in vcl_recv section).I can't comment so I'm posting this as an answer.
Attention: As of 2.1.0, obj.* is called beresp.* in vcl_fetch, and obj.* is now read-only.
These also seem better placed in vcl_recv instead of vcl_fetch:
Lastly, in you backend definition, I would add
Adjust to the number of passenger backends you have allowed nginx/apache to create. If you don't set this limit you should keep an eye on you passenger global queue (assuming you're using passenger).