Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

HTTP caching for anonymous visitors

Read this page if you want browsers, proxies or a CDN to keep pages and static files for anonymous visitors, or if you wonder why a returning visitor's browser asks for every stylesheet and image again. Two settings…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/bc/6.0/http-caching.md

On this page

HTTP caching for anonymous visitors

Read this page if you want browsers, proxies or a CDN to keep pages and static files for anonymous visitors, or if you wonder why a returning visitor's browser asks for every stylesheet and image again. Two settings decide what a visitor's browser may keep: [HTTPHeaderSettings] in the application (pages) and StaticMaxAge in Velocity (static files). Neither changes anything for a logged-in user.

In short

What changedThe page headers can be made cacheable for anonymous visitors only; ezpKernelWeb::run() drops the default Pragma when a configured header overrides Cache-Control; Velocity sends a long lifetime for static files.
Who is affectedSites that want caching for anonymous visitors. An installation that configures nothing sends the same page headers as before.
How to checkcurl -sI https://your-host/ | grep -i -E "cache-control|expires|pragma" as an anonymous visitor.
How to fixTurn on the page headers as shown below; lower StaticMaxAge while you work on a stylesheet.

The problem

Every page response carried:

Cache-Control: no-cache, must-revalidate
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Pragma: no-cache

and every static file carried:

Cache-Control: public, max-age=0, must-revalidate

So no browser, proxy or CDN was allowed to keep a page for any length of time, and every stylesheet, script and image had to be revalidated on every view. On the front page that is 41 conditional requests per visit, each a round trip answered 304. The bytes were saved; the latency was not, and on a page with dozens of assets the latency is the whole cost of the first paint.

Pages: [HTTPHeaderSettings]

The kernel has always had these settings; they are off by default (CustomHeader=disabled in settings/site.ini). To switch them on, add to settings/override/site.ini.append.php:

[HTTPHeaderSettings]
CustomHeader=enabled
OnlyForAnonymous=enabled
OnlyForContent=enabled

HeaderList[]
HeaderList[]=Cache-Control
HeaderList[]=Expires

Cache-Control[]
Cache-Control[/]=public, max-age=60
Expires[]
Expires[/]=60

Then clear the INI cache:

php bin/php/ezcache.php --clear-tag=ini --allow-root-user
  • OnlyForAnonymous is what makes this safe. eZHTTPHeader::enabled() resolves it to !eZUser::isCurrentUserRegistered(), so a logged-in editor always receives the original no-cache headers, and a personalised page is never handed to a shared cache.
  • Expires is given in seconds; the kernel converts it to a date.
  • The trade is staleness. An anonymous visitor may see a page up to max-age seconds old after it was edited. Sixty seconds is short enough that nobody publishing notices, and long enough to absorb a burst of traffic.
  • To go back, set CustomHeader=disabled.

How to check

Check both directions: the anonymous response must be cacheable, and a logged-in response must never be.

curl -sI https://your-host/ | grep -i -E "cache-control|expires|pragma"

Expected for an anonymous visitor with the settings above: Cache-Control: public, max-age=60, an Expires date 60 seconds ahead, and no Pragma: no-cache. Repeat the request with the session cookie of a logged-in user (copy it from the browser): the response must show Cache-Control: no-cache, must-revalidate.

The kernel change behind it

Pragma: no-cache is the HTTP/1.0 spelling of Cache-Control and cannot express "cacheable". Left beside public, max-age=60, the response contradicted itself. Browsers resolve that in favour of Cache-Control, but a proxy may read the Pragma and decline to store the page, which defeats the point of the header.

ezpKernelWeb::run() now drops the default Pragma when, and only when, a configured header has overridden Cache-Control. A response that keeps the default Cache-Control keeps its matching Pragma, so an installation that has configured nothing is unaffected.

Static files: StaticMaxAge (Velocity)

FileBlockKeyDefaultScope
settings/velocity.iniServerSettingsStaticMaxAge31536000 (one year, in seconds)installation

Velocity writes the value into the server's configuration as Q.web.static.maxAge. 0 leaves the server's own default, which permits caching and then makes the client revalidate anyway.

A year is what the other web server in front of this installation already sends for the same files. The trade is that a file replaced in place is not noticed until the lifetime expires: clear the browser cache, or lower the number, while you work on a stylesheet.

What it is worth

Counted per asset on the front page:

AssetsReusable with no requestMust revalidate
Before41041
After41410
nginx, for comparison41410

This does not make a first visit faster. It makes every later visit and every in-site navigation cost 41 fewer round trips, which a returning visitor experiences as the page painting immediately.

What this is not

  • Not the server's response cache, which stores rendered responses in the parent process; that is a separate feature, off by default (Velocity response cache).
  • Not the kernel's static cache (StaticCache in site.ini), which writes flat files.
  • Not the role-aware HTTP cache, which keeps whole pages for signed-in visitors as well and serves them before the kernel starts: see HTTP cache.
  • Not a reduction of the rendering work. The front page costs about 529 database queries, and no caching header changes that for a visitor who misses the cache.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x