Caching a whole rendered page turns a 400ms request into a 5ms one. It also means everyone gets the same page - which is exactly right for an article and exactly wrong for a basket, a dashboard or anything that says the visitor's name.

The rule

Cache a response only if it would be identical for a stranger. If any part of it depends on who is asking, either do not cache it, or take that part out of the cached HTML and fetch it separately.

What must never be cached

  • Anything behind a login, including the page that shows a login state in the header.
  • Basket, checkout, account and order pages.
  • Anything with a CSRF token in it - a shared token is not a token. See CSRF and forms.
  • Search results with personal history in them.

Tell the cache what varies

# skip the cache when a session cookie is present\nfastcgi_cache_bypass $cookie_session $http_authorization;\nfastcgi_no_cache   $cookie_session $http_authorization;

Prove which one you got

add_header X-Cache-Status $upstream_cache_status;\n\ncurl -sSI https://yourdomain.com/ | grep -i x-cache

HIT, MISS and BYPASS in that header answer in one second what an afternoon of reading configuration will not. If a logged-in page says HIT, stop and fix it before doing anything else.

The invalidation half

A cache that is never cleared shows yesterday's price. Purge on publish, and know how to purge everything by hand when a deploy changes the templates.

Test while logged in, in a normal window - not a private one. Almost every cache leak of this kind was found by a customer, not by the developer, because the developer only ever tested logged out.
On EGPHP hosting the page cache is configured with the login and checkout paths already excluded. This article is for a VPS where the rules are yours to write.