Why You Should Use 'Cache-Control: public' for Caching to Work with SSL
We've been using long Expires and Cache-Control: max-age headers to mark our javascripts and css as browser-cacheable for quite some time. This way the browser only does full GETs on these once, and avoids conditional GETs except on page refresh. This only requires a few lines of configuration in Rails (asset timestamps and asset packaging) and nginx and generally works well.
Except when it does not. We've had some bug reports that in some cases under SSL these files were not cached at all. Meaning, just going to a different page in our application would make browser do an unconditional GET to refetch css and javascript. Naturally, this would hurt the performance.
Now, SSL in itself makes things worse. Firefox only caches SSL content in memory not on disk, meaning if the user closes the browser the cache goes away. But the reported behavior was worse than that -- no caching at all.
It took us a while, but we did manage to chase it down. It seems that Firefox divides SSL content into page marked with Cache-Control: public, which have priority for caching, and the rest. When you open enough tabs, say 50+, with enough rich pages in Firefox, its in-memory cache fills up and refuses to cache non-public SSL content. We haven't had time to fully chase it down in the FF codebase, but what's likely to happen is that FF actually does put in into cache, then realizes that cache is full, looks for pages to evict, and right away evicts the newly added content. Since SSL content is not cached on hard disk, once the memory cache is full, the non-pubic SSL files are not cached at all. (Note that's just a hypothesis, we'd be most curious to know what actually happens and why.)
The server-side workaround is to add Cache-Control: public to all your SSL content you want cached. We do:
# nginx configuration for static content
location /static {
expires 1y;
add_header Cache-Control public;
}
This bug is present in Firefox 2 and 3, but as a user you can configure them to work around the problem by setting browser.cache.memory.capacity to increase the size of memory cache or using browser.cache.disk_cache_ ssl to enable disk cache for SSL pages. The default 24M memory cache size for systems with 2G of RAM is really small, especially with FF3 being able to handle hundreds of tabs well. Note that the disk_cache option works only on Firefox 3 because of some bugs in earlier browser versions.
Small remark for Acunote users - you don't have to do anything about this. We enabled Cache-Control: public and your Firefox will cache javascript and css files no matter how many tabs you have.
I don't think it is a bug.
SSL content is assumed to be confidential, and should not be saved to disk. It seems a bit rash to tell people to enable this, without explaining the risks.
And if the in-memory cache is full, something has to be evicted. If you have 50+ tabs open, you could easily have have 1MB of JS per tab. Once the working set is bigger than the available cache, the cache will just thrash and be useless.
Posted by: Tom | July 15, 2008 at 06:08 PM
Agreed,
This is not a bug. Caching SSL data to disk is not secure. To avoid caching issues in the 1990's we would use SSL to stop aggressive caching by ISP (dial-up days). If you are have performance issues with your application because of files not caching, I would recommend you re-architect your application.
Posted by: Brian | July 16, 2008 at 07:19 AM
Tom, Brian, I agree, disk caching of SSL content is not secure and I'm not recommending it, just saying it can be a workaround.
What I did want to say is that Firefox has too small memory cache size and if you don't use "Cache-Control: public" on your side, Firefox may never manage to keep the pages in memory cache for your users.
Users with 5 tabs will never see that, users with 50 tabs will think your web application is slow just because the content that should have been cached (in memory) will never be cached at all. So as an application developer you really have only two choices - ask those users to increase memory cache or put "Cache-Control: public".
Posted by: Alexander Dymo | July 16, 2008 at 07:38 AM
Tom: the article describes (perhaps not clearly enough), that FF appears to use MRU eviction algorithm for this content, which likely is a bug. Meaning, it's evicted immediately, and never cached. We are not talking about the normal LRU cache behavior.
Posted by: Gleb Arshinov | July 16, 2008 at 03:46 PM
FYI, the relevant code is at:
http://mxr.mozilla.org/firefox/source/netwerk/protocol/http/src/nsHttpChannel.cpp#2136
Posted by: Mark Nottingham | June 19, 2009 at 06:42 AM