I have a requirement where I need to support downgrading of my server code.

I have the following line in my nginx configuration file, indicating that the browser can cache the page but has to validate with server to check if the file changed.

add_header Cache-Control "no-cache";

This setup works perfectly fine for me with all the upgrades done on my server code.

But when it comes to downgrading a resource to an older version, when the browser tries to validate the resource change, nginx says that the resource hasn't changed, so the browser shows the cached(newer) resource instead of the downgraded(older) resource.

As a workaround, I could use the following setting to disable cache completely, but its not efficient, and I would like to have caching.

Just to add mode retails. With "no-cache" setting, when a resource is upgraded, browser gets 200 OK status and when a resource is downgraded, browser get 304 NOT MODIFIED status.
–
SchuJan 7 '12 at 21:08

I believe nginx uses timestamps to determine modification - try to 'touch' the file - update the timestamp - and see if it fixes your issue. (I haven't quite managed to figure out your scenario - but I am presuming you have older static files, you upgrade them (keeping the old files), and then at some point change your links to point back to the older files). Add some detail to your question describing whether these are static or dyanmic files, a sample url (do you use query strings), and something about this 'downgrade' process. (Also see if you have any luck with adding 'must-revalidate').
–
cyberx86Jan 8 '12 at 2:52

By upgrade I mean, replace the server code(includes static resources) with newer version from source control, By downgrade I mean the opposite, replace the code with older version from source control. At any point of time, there is only one set of files on the server. No I dont have any query strings. All references are from html files to javascript files using script tags.
–
SchuJan 9 '12 at 4:05

I use dpkg for upgrade/downgrade process and it also probably uses timestamps to detect code change. I fear 'touch'ing the file would mess it up. I will try it though.
–
SchuJan 9 '12 at 4:09

I tried must-revalidate, it behaves identical to no-cache, so it doesn't work. I tried touching the files and it work fine, i still have to verify its impact on dpkg though.
–
SchuJan 9 '12 at 14:44

2 Answers
2

In a no-cache scenario, the browser will usually issue an If-Modified-Since header with a GET request, to see if the file in question has changed from the cached copy.

The server will either:

return a 304 - Not Modified response

return another code:

200 - OK if the modified date on the server is later than that passed with the header

Other - any other code that might be returned (4xx, 5xx, etc)

Nginx will use the modified timestamp on the file it is serving to generate the Last Modified header and for comparison against If-Modified-Since.

Due to this, updating the timestamp of a file with touch /path/to/myfile.ext will cause nginx to identify it as having been modified after the If-Modified-Since date and will allow nginx to serve the file.

Alternatively, you should be able to force a re-fetch by explicitly specifying a 'Last Modified' header in your nginx config that is after the anticipated date of If-Modified-Since. In your scenario, this would essentially entail:

An important point of mention is that if your static assets change after this, the hard-coded header will not reflect the change (i.e. even when you 'upgrade' your code, you will still have to manually alter the config.

Actually in the scenario of static file being updated after the Last-Modified being set on the config file, the server issues a 200-OK, given we also have the no-cache setting. This means that even if one of Cache-Control or Last-Modified says the file is modified, the final server response would be 200 OK.
–
SchuJan 9 '12 at 17:41

@Schu: Interesting, no-cache only requires that the browser revalidate all requests with the origin server. I would expect that if Last-Modified can override an 'older' timestamp, that it should also do the same for a newer one (and therefore return 304). I think there is something additional going on - as I usually would not expect setting a response header to alter the way a request header is processed.
–
cyberx86Jan 9 '12 at 17:53

I was thinking of it as "override" as well, but its the server response that led me to the conclusion of "either-or". Assuming older version is in cache, i get the following responses 1. just no-cache - 200 ;2. just last-modified - 304; 3. both no-cache and last-modified - 200
–
SchuJan 9 '12 at 20:27

First of all, "no-cache" doesn't necessarily mean what you think it means in practice. I suspect you really want to use "Cache-Control: max-age=0, must-revalidate". Some browsers (Firefox) incorrectly treat "no-cache" as an equivalent of "no-store", and don't cache anything on disk at all. They simply re-fetch all content from the source at every page load, which is a terrible waste of bandwidth and sucks for the user experience.

Secondly, your particular use case is exactly what ETags were designed for, rather than the simple forward-only date logic that is used without them. (The entire linked site is great reading on caching, not just on e-tags).

Finally, you should consider naming your resources with some form of date identifier (or better yet content hash) so that you never have to worry about this sort of thing. You can then set cache-control to 1 year. This requires workflow changes and build scripts though for many sites to rename files and replace links. But that is what Google and the other big boys do...

Thanks for site link. There's lot of stuff there for me to know. I was testing on chrome and its doing it right ie., can cache but re-validate. But you are right, I have to avoid no-cache for efficiency on browsers with no-store interpretation. Coming to Etags, unfortunately the nginx I use doesn't generate them, so all I can do is put a static ETag which in essence would be equal to a LastModified setting.
–
SchuJan 11 '12 at 22:11