CDN Caching#
The Cache-Control you declare in HTTP Caching & ETag also works directly on a CDN's (Cloudflare etc.) edge cache. For a read-heavy site, you can hit on the edge and reduce reaching the origin (PHP / remote DB) to nearly zero. However, putting HTML on the CDN requires CDN-side configuration, and there is a Relayer-specific trap where a session cookie causes a full BYPASS, so this focuses on those two points.
Separating browser and CDN (max-age vs s-maxage)#
Cache's maxAge is the browser's freshness, sMaxAge is the shared (CDN) cache's freshness. A browser cache cannot be purged (once served, a fix never arrives), whereas a CDN can be purged, so setting them asymmetrically is the standard practice.
// short for the browser, long for the CDN; edits reflect immediately via a CDN purge
new Cache(public: true, maxAge: 300, sMaxAge: 2592000, etag: $etag);
// → Cache-Control: public, max-age=300, s-maxage=2592000Centralize the shared policy in one place (e.g. App\PageCache::timed()) and keep the TTL as the only knob there to make operations easy.
Making Cloudflare cache HTML#
Cloudflare by default caches only static assets (extension-based) and treats HTML / JSON as "dynamic," passing them through. Adding s-maxage alone is not enough. Create a Cache Rule for the target paths (the equivalent of the old Page Rule's "Cache Everything" = Eligible for cache) and set the Edge TTL to respect origin (Use origin cache-control). Now Cloudflare adopts s-maxage and caches at the edge for the specified number of seconds.
- Target every read path (e.g.
/,/docs/*,/search,/api/*). If the rule's path scope covers only part of them, the pages outside it stayMISSforever and are never cached. - Purge the zone on every deploy (e.g. have CI hit Cloudflare API's
purge_everything). Stale HTML left at the edge keeps serving the previous deploy's forms androsanna-defer.js, so form submissions and defer fetches disagree with the new server.
The biggest trap: session cookie causes a full BYPASS#
Cloudflare unconditionally bypasses caching for any response that includes a Set-Cookie (cf-cache-status: BYPASS). Here is the Relayer-specific pitfall.
Relayer's page rendering (Rosanna) uses no session and no state. session_start() runs only when a page touches the session, such as authentication (Authenticator) or a server action's CSRF token (_rosanna_csrf). A page that does not touch it emits no Set-Cookie: PHPSESSID, so it is edge-cacheable. Only pages that do emit Set-Cookie and are sent with Cache-Control: private, no-store, so those are BYPASSed as intended.
Points:
- Do not block this by setting
session.use_cookies=0on the app side. The CDN may go through, but it silently breaks pages that later use auth / CSRF. The correct fix is "leave it to the framework." - Separately, keep
session.cache_limiterempty. When a session starts, PHP injectsCache-Control: no-store, no-cache, must-revalidate, overriding the per-page cache policy.
// public/index.php, before Relayer::boot()
\ini_set('session.cache_limiter', '');Verifying it works#
Measure the response headers for real.
curl -sI https://example.com/docs/some-page | \
grep -iE 'cache-control|cf-cache-status|age|set-cookie'- There is no
set-cookie:(if present, it is the cause of BYPASS). cf-cache-status:isMISS→HITon re-access, withage:increasing.cache-control:carries the intendeds-maxage.
DYNAMIC means the Cache Rule is not set (HTML not targeted), BYPASS means cache excluded due to Set-Cookie etc. Right after purging the zone the edge is unpopulated, so MISS continues for a while — wait a few tens of seconds and check again.
Reflecting edits and purging#
Within max-age / s-maxage the origin is not queried, so even after you update content the CDN stays stale for at most s-maxage seconds. If you have CI that purges the zone on deploy, deploy reflection is immediate, but in operations that update only content without a deploy (editing the external DB directly, etc.) that update does not appear on the CDN until you purge. If you use a long s-maxage, always build a CDN purge into the content-update path too.
Related: HTTP Caching & ETag · Deployment (Dockerfile)
Revisions (3)
- v1.1.0: usePHP→Rosanna(rosanna-defer)に合わせて更新
- Sidebar reorg: order shift for the new Development category
- Created