SS3 CloudFlare 304 caching gotcha

<%- if @topic_view.topic.tags.present? %>
<%= t 'js.tagging.tags' %>: <%- @topic_view.topic.tags.each do |t| %> <%= t %> <%- end %>
<% end %>

Was working on an SS3 site today and encountered a strange issue. If I logged in to the site, I would get my ‘logged in’ components in the front-end (e.g. BetterNavigator). But if I logged out of the CMS and refreshed a front-end page, those components were still there (I still appeared to be logged in).

After a bunch of debugging I concluded that SS3 sends incorrect cache headers if all these conditions are met:

  1. You have a PHPSESSID cookie (e.g. from logging in)
  2. You are viewing a front-end page (not a CMS page)
  3. The site is in ‘live’ mode (not dev or test)

Instead of:

Cache-Control: no-cache, no-store, must-revalidate

Which means ‘under no circumstances should you cache this page’

You get:

Cache-Control: must-revalidate, private

Which means ‘you can cache this page at the end of the line (browser cache) but you need to check it hasn’t changed before serving it up.’

So the caching directive is stricter when you’re not logged in, which doesn’t make sense to me.

The response typically doesn’t include an ETag header (though does sometimes, not sure what triggers that), so as far as I can tell, the client looks at the ‘must-revalidate’ directive, then looks at the ‘Last-Modified’ header, and may decide to serve up a stale copy of the page from the browser cache based on those values.

Here is the gotcha. This doesn’t seem to matter in Google Chrome or Safari when fetching the page from the origin server - I don’t get the stale page. But if I enable the CloudFlare proxy, I get this bug. Notably, the origin server sends the response with a 200 status code, but CloudFlare attaches a 304 status code instead when passed through the proxy - that seems to be what enables the bug. It appears that CloudFlare is interpreting these headers differently to how the browser would (at least, the two browsers I tested).

I’m guessing this is happening on a lot of SS3 sites where CloudFlare is enabled, but if the front-end doesn’t change when not logged in, it wouldn’t be easily noticeable. This also might be something that has recently changed in CloudFlare’s parsing functionality.

The impact should be low since the ‘private’ directive should mean that the cached copy can’t leak between users. But if your client is asking about why they appear to be logged in still and you’re banging your head against a wall, hopefully this post will help you.

Adding this to the PageController init function seems to fix this issue:

public function init()
{
    // Ensure CloudFlare doesn't accidentally send a 304
    HTTPCacheControl::singleton()->disableCache();

    parent::init();
}
2 Likes