Drupal's own internal page cache is fast, but it still means PHP boots, loads the container, and runs a full bootstrap for every single cached request. A reverse proxy cache like Varnish sits in front of your entire web server and can answer a cached request without PHP ever starting — the difference between a fast database lookup and no application execution at all.
What you'll learn in this lesson
- Where Varnish sits in the request path, and why that position matters
- The minimal VCL needed to respect Drupal's own cache headers
- Why cache invalidation gets harder, not easier, with a proxy in front
Where it sits
The request path becomes: browser → Varnish → (only on a cache miss) → your web server → PHP-FPM → Drupal. Every request Varnish can answer from its own cache never reaches PHP at all — no bootstrap, no database connection, nothing. This is strictly faster than even Drupal's internal page cache, which still requires a minimal Drupal bootstrap to check the cache and serve the result.
Respecting Drupal's own cache headers
sub vcl_backend_response {
# Trust Drupal's own Cache-Control: max-age headers rather than
# imposing a fixed TTL — Drupal already computed the correct value
# from every #cache max-age in the render tree.
if (beresp.http.Cache-Control ~ "max-age") {
set beresp.ttl = std.duration(
regsub(beresp.http.Cache-Control, ".*max-age=(\d+).*", "\1") + "s",
120s
);
}
}
sub vcl_recv {
# Never cache requests carrying a session cookie — an authenticated
# visitor's page must never be served to someone else.
if (req.http.Cookie ~ "SESS") {
return (pass);
}
}
return (pass)). Cache an authenticated page and serve it to a different visitor, and you've built a genuine data-leak — one user's personalized or private content shown to someone else entirely. Every production Varnish config for Drupal starts from this rule.Cache invalidation: the part that gets harder
Drupal's own cache tags system knows how to invalidate its internal cache precisely. Varnish, sitting entirely outside Drupal, doesn't know anything about cache tags unless you explicitly wire it up — typically via the contributed Purge module, which listens for Drupal's cache-tag invalidations and translates them into BAN or PURGE requests sent to Varnish. Skip this integration, and editing a node updates Drupal's own cache correctly but leaves Varnish serving the stale version until its TTL naturally expires.
Quick check: an editor updates a node, and Drupal's own internal cache reflects the change immediately — but visitors still see the old version for the next hour. What's missing? If you said the Purge module (or equivalent Varnish cache-tag integration) isn't wired up, so Varnish has no idea Drupal's cache tags were invalidated, you've identified the single most common Varnish-with-Drupal misconfiguration.
Key takeaways
- A reverse proxy cache answers requests before PHP ever starts — strictly faster than any in-application cache.
- Always bypass the cache for any request carrying a session cookie — this is a hard security rule, not a tuning choice.
- Configure Varnish to respect Drupal's own
Cache-Control: max-ageheaders rather than imposing a fixed TTL. - Without the Purge module (or equivalent) wiring Drupal's cache tags to Varnish invalidation, edits won't actually clear the proxy cache.
Coming up next
You've now covered OpCache, PHP-FPM, HTTP/2, and reverse proxy caching. Next, we zoom out to the underlying server resources themselves — CPU, memory, and disk I/O tuning for a Drupal workload specifically.