Varnish Cache VCL configuration optimized for WordPress and WooCommerce. Everything lives in a single default.vcl file for simplicity.
- WordPress-aware caching: Bypass cache for logged-in users, AJAX, auth, and WooCommerce sessions
- Cookie normalization: Strips tracking and analytics cookies (GA, WooTracker, etc.) for better hit ratio
- Purge / BAN: PURGE and BAN methods restricted by ACL (localhost by default)
- Static & big files: Dedicated handling for static assets (Cache-Control respected; 1d default when omitted); large files (>10 MB) delivered uncacheable
- WebSockets: Pipe for
Upgrade: websocketrequests - Tracking params: Removes common UTM and campaign query parameters from the cache key
- Grace & TTL: Configurable grace period, 500/404 handling, and Cache-Control respect
- Accept-Encoding normalization: Reduces cache fragmentation (br > gzip > none)
- Backend health checks: Automatic probe to detect backend failures
- imgproxy (opt-in): Dedicated backend for WordPress upload images — URLs automatically rewritten to imgproxy format (supports processing params)
- Security: HTTPOXY mitigation, debug headers only exposed with
X-Debugrequest header
- Varnish Cache 4.x (or compatible)
- Varnish modules:
std,directors(and optionallyxkeyif you use it)
.
├── default.vcl # Single-file VCL: ACL, backend, recv, hash, backend_response, deliver, pipe
├── CHANGELOG.md
├── README.md
└── LICENSE
-
Copy
default.vclto your server (e.g./etc/varnish/default.vcl). -
Edit the
backendandacl purge_aclsections at the top of the file to match your environment. -
Start or reload Varnish:
varnishd -f /etc/varnish/default.vcl # or varnishreload
All configuration is at the top of default.vcl:
- Backend: Adjust
.host,.port, and.probein thebackend backend1block. - Purge ACL: Add allowed IPs in the
acl purge_aclblock for PURGE/BAN. - Debug: Send an
X-Debug: 1request header to seeX-Cacheablein the response.
By default, when the backend sends no Cache-Control header, the VCL forces a 1 hour TTL so the response is cached. This avoids uncacheable pages when WordPress (or the backend) omits cache headers.
To disable this and only cache responses that explicitly send cache headers (or a positive TTL):
- In
default.vcl, invcl_backend_response, find the block# -- Default TTL when the backend sends no Cache-Control --. - Comment out the entire
if (!beresp.http.Cache-Control) { ... }block (the three lines inside theifand theifitself).
After disabling, responses without Cache-Control keep beresp.ttl = 0 and are marked uncacheable by the next block; they will not be stored in the cache.
Image processing via imgproxy is supported but disabled by default. When enabled, WordPress upload images (content/uploads/...) are routed to a dedicated imgproxy backend and URLs are rewritten to the imgproxy format automatically.
To enable, uncomment these three blocks in default.vcl:
- Backend definition — search for
backend imgproxyand uncomment the block. Adjust.hostand.portto match your imgproxy instance. - Routing in
vcl_recv— search forset req.backend_hint = imgproxyand uncomment theifblock. - URL rewriting in
vcl_backend_fetch— search forsub vcl_backend_fetchand uncomment the entire sub.
Supported image extensions (imgproxy Community): jpg, jpeg, png, webp, gif, avif, tiff, tif, bmp, ico, heic, heif, svg.
URL rewriting handles both single-site and multisite WordPress installs, with or without processing parameters:
| Incoming URL | Rewritten URL sent to imgproxy |
|---|---|
/content/uploads/2025/10/photo.jpg |
/insecure/plain/local:///content/uploads/2025/10/photo.jpg |
/rs:fill:500:500/content/uploads/2025/10/photo.jpg |
/insecure/rs:fill:500:500/plain/local:///content/uploads/2025/10/photo.jpg |
/content/uploads/sites/2/2025/10/photo.jpg |
/insecure/plain/local:///content/uploads/sites/2/2025/10/photo.jpg |
- Pages: Protocol (X-Forwarded-Proto), Accept-Encoding (normalized), optional Cookie (when WordPress-related cookies are present), plus default (host, URL, etc.).
- Static files: Protocol, Accept-Encoding (normalized), and URL path only (no cookie, no host in hash).
By default, static files are cached without the host in the hash key. This means a file at the same path on two different domains shares a single cache entry:
domain1.com/app/uploads/photo.jpg ──┐
├──> same cache entry
domain2.com/app/uploads/photo.jpg ──┘
This is optimal for WordPress multisite setups where uploads are physically shared across domains (same files, same paths).
If your domains serve different static files at the same path (e.g. different themes per domain), you need per-domain caching. In vcl_hash, find the else branch for static files and add hash_data(req.http.host):
} else {
hash_data(req.url);
hash_data(req.http.host);
return (lookup);
}This change is also documented inline in default.vcl (search for "cache static files per domain").
GPL v2. See LICENSE.