yumupgrades for production use, this is the repository for you.
Active subscription is required.
I figure there isn’t much all-in-one information on the subject and this will be a constant draft with my findings.
PHP 7 and caching headers
PHP itself alters Cache-Control headers only when all conditions are true at the same time during request:
- session_start() has been called
- session.cache_limiter has default value of nocache
It adds 3 caching related headers:
Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache
In short, the default behaviour is to send anti-caching headers any time sessions are in use, not only when Set-Cookie is being sent for the first time. Anytime!
When session_start() is not leveraged, PHP does not touch Cache-Control and friends at all.
The possible values for session.cache_limiter and session_cache_limiter() are:
none: no header will be sent
Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache
Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: private, max-age=10800, pre-check=10800
Cache-Control: private, max-age=10800, pre-check=10800
Expires: pageload + 3 hours Cache-Control: public, max-age=10800
WordPress Cache Headers
WordPress does not send caching headers except for a few specific areas where caching has to be disabled.
Those areas include:
- Error pages (called via
- A page delivering the fact that database connection could not be established
- Response to
POST-ing a comment
- When user is is logged in
- 404 pages
In all those cases, the following anti-caching headers are sent:
'Expires' => 'Wed, 11 Jan 1984 05:00:00 GMT', 'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
Thus, if you’re seeing expiration date is
19 Nov vs
11 Jan in
Expires header, you can easily guess what sent the anti-caching headers (PHP vs WordPress).
At the same time it nulls the
Last-Modified header (if e.g. a plugin set it).
The sending of anti-caching headers is implemented in
nocache_headers() function, which is called in the mentioned areas.
You can globally override the anti-caching headers by using
Typically, you don’t need to adjust the
nocache_headers though. Their primary purpose is to instruct browsers and shared caches (like Varnish) to not cache something that should not be cached at all!
WordPress does not send
Cache-Control or other cache related headers for regular pages like homepage or posts.
Which means that, in case of Varnish, the default cache TTL applies.
The de-facto standard approach to caching WordPress is adjusting cache TTL to maximum, in Varnish (e.g. 2 weeks).
This requires cache invalidation strategy, should content of an article, or website in general, change.
Varnish has no idea when you update an article contents, or change theme. Typical solution to this lies in using cache invalidation plugin like Varnish HTTP Purge. It will hook into necessary WordPress events (post update for example) and “talk” to Varnish to clear respective page’s cache upon update. Both plugin and Varnish VCL amendments required.
A slightly more flexible variation of the above approach, is having WordPress send
Cache-Control headers to dictate how long regular pages are to be cached by Varnish, instead of hardcoded TTL value in Varnish. This can be achieved through a plugin like this one, which would allow setting custom cache expiration values.
E.g. to cache a page for 2 weeks in Varnish and 10 seconds in browsers, you may send:
Cache-Control: s-max-age=1209600, max-age=10
In this example we use
s-max-age, which allows to specify cache lifetime for shared caches (Varnish) differently.
Last-Modified header in WordPress
Last-Modified for feeds only. The implementation is at