Site icon GetPageSpeed

PHP and HTTP caching

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:

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

nocache:

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

private:

Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: private, max-age=10800, pre-check=10800

private_no_expire:

Cache-Control: private, max-age=10800, pre-check=10800

public:

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:

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 nocache_headers filter.

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

WordPress implements Last-Modified for feeds only. The implementation is at ./wp-includes/class-wp.php.

Exit mobile version