0

My backend application sends Cache-Control headers so that NGINX and the browser can cache responses.

I would like CloudFlare to not cache these responses. This is because they may have non-public information and while NGINX is cookie-aware when caching, CloudFlare is not (at least not on the free or pro plan)

Essentially I want NGINX to see this so that it caches the response:

Cache-Control: public, max-age=x (This is what my backend app sends)

And for CloudFlare and the browser to see: (So the browser caches the response, but CloudFlare wont because it is private)

Cache-Control: private, max-age=x

e.g. CloudFlare should always MISS on these requests, but possibly HIT when it gets to nginx.

I think this could be accomplished if NGINX could replace "public" with "private", or recreate the header is the "max-age" variable could be captured. It could also be done if my app sends "private", but NGINX is instructed to cache private responses anyways. I have not been able to find a way to do either however.

Cyral
  • 101
  • 2
  • If the response does contain non-public information, then the browser should also receive `Cache-Control: private`. But if you make nginx cache them as `public`, how can you then avoid sending a cached response to the wrong person? – Michael Hampton Oct 01 '20 at 01:58
  • @Michael Hampton nginx can cache based on cookie. e.g. $cookie_sid$url as the key. EDIT: I may have misinterpreted that, yes the browser should see private too actually. Although it doesn’t matter what the browser sees really – Cyral Oct 01 '20 at 02:40

2 Answers2

0

OK, this turns out to be quite easy: nginx already has a directive to ignore the upstream Cache-Control and Set-Cookie headers sent by the web app when deciding whether to cache, proxy_ignore_headers.

proxy_ignore_headers Cache-Control Set-Cookie;

This also requires that a cache lifetime be set with proxy_cache_valid, for example:

proxy_cache_valid any 1d;

So after this you can (and should) just send Cache-Control: private; ... from your web app, and nginx will cache but downstream caches like CloudFlare will not.

Note that your proxy_cache_key needs some way to differentiate responses sent to different users with the same URL, otherwise one user may see another user's data.

For a lot more information, be sure to read NGINX's Guide to Caching.

Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
  • Unfortunately since this requires the `proxy_cache_valid` duration, it doesn't quite work very smoothly since there are various `max-age`s sent by the backend server depending on resource. (In fact, my backend even sends different max-ages depending on the user, e.g. logged out users can have more stale resources but logged in users require more up to date resources) – Cyral Oct 01 '20 at 20:26
  • @Cyral Hm. In that case you almost certainly need something that can do more complex caching (maybe Varnish? I'm not that familiar with it though) or possibly to implement this caching directly in your application. – Michael Hampton Oct 01 '20 at 20:52
0

I ended up with a solution that feels a bit hacky, but it works.

It uses regex inside a map to replace "public" with "private" in the Cache-Control header.

map $upstream_http_cache_control $cachemap {
  ~(public)(.*)  private$2;
  default "";
}

...


proxy_hide_header 'Cache-Control';
add_header Cache-Control $cachemap always;

Cyral
  • 101
  • 2