Varnish Cache is an HTTP accelerator: it stores responses in memory and serves repeated requests without touching your application. Cached pages are delivered in microseconds, so a modest server can handle large traffic spikes. Varnish doesn’t speak HTTPS itself, so the usual layout is:
Client ──HTTPS──► Nginx :443 ──HTTP──► Varnish :6081 ──► Backend (Nginx/Apache/PHP or app) :8080
Do you need Varnish?
- Yes: high-traffic content sites, news, catalogues, APIs with cacheable responses — where a dedicated, programmable cache pays off.
- Maybe not: many sites get most of the benefit from a simpler option: Nginx FastCGI cache, a WordPress cache plugin, or a CDN. See Nginx FastCGI cache.
1. Install
sudo apt update
sudo apt install -y varnish
varnishd -V
(The Varnish project’s packagecloud repositories provide newer versions.)
2. Move the backend to port 8080
Your application server (e.g. Nginx serving PHP) should listen locally on 8080:
server {
listen 127.0.0.1:8080;
server_name example.com;
root /var/www/example.com;
# … PHP config …
}
3. Configure Varnish’s listen address and memory
Override the systemd unit:
sudo systemctl edit varnish
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a 127.0.0.1:6081 -f /etc/varnish/default.vcl -s malloc,512m
-s malloc,512m sets the cache size in RAM. Size it to your working set and available memory.
4. Basic VCL
/etc/varnish/default.vcl:
vcl 4.1;
backend default {
.host = "127.0.0.1";
.port = "8080";
.probe = {
.url = "/health";
.interval = 5s;
.timeout = 2s;
.window = 5;
.threshold = 3;
}
}
acl purge {
"127.0.0.1";
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge) { return (synth(405, "Not allowed")); }
return (purge);
}
# Never cache admin, login, cart or checkout
if (req.url ~ "^/(wp-admin|wp-login|cart|checkout|my-account)") {
return (pass);
}
# Logged-in users bypass the cache (WordPress example)
if (req.http.Cookie ~ "wordpress_logged_in_|woocommerce_items_in_cart") {
return (pass);
}
# Strip cookies from static files and anonymous requests
if (req.url ~ "\.(css|js|png|jpg|jpeg|gif|webp|svg|woff2)(\?.*)?$") {
unset req.http.Cookie;
}
unset req.http.Cookie;
}
sub vcl_backend_response {
if (bereq.url ~ "\.(css|js|png|jpg|jpeg|gif|webp|svg|woff2)(\?.*)?$") {
set beresp.ttl = 7d;
} elsif (beresp.ttl <= 0s) {
set beresp.ttl = 5m;
}
set beresp.grace = 6h; # serve stale content if the backend is down
}
sub vcl_deliver {
if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; }
}
The single biggest rule of Varnish: requests with cookies aren’t cached by default. Deciding which cookies matter (logins, carts) and stripping the rest is most of the configuration work.
sudo varnishd -C -f /etc/varnish/default.vcl > /dev/null && echo "VCL OK"
sudo systemctl daemon-reload && sudo systemctl restart varnish
5. Nginx for TLS in front
server {
listen 443 ssl;
http2 on;
server_name example.com;
# ssl_certificate … (Certbot)
location / {
proxy_pass http://127.0.0.1:6081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
The backend app must trust X-Forwarded-Proto to know the request was HTTPS.
6. Verify
curl -sI https://example.com/ | grep -i x-cache # MISS, then HIT on repeat
varnishstat -1 | grep -E 'cache_hit|cache_miss'
varnishlog -g request -q "ReqURL eq '/'"
varnishlog shows why a request was passed or cached — essential for debugging cookie issues.
7. Purging
When content changes, purge it:
curl -X PURGE http://127.0.0.1:6081/blog/my-post/ -H "Host: example.com"
CMS plugins (e.g. “Proxy Cache Purge” for WordPress) send purges automatically on updates. For tag-based invalidation, look at Varnish’s xkey module.
Pitfalls
- Personalised content cached for everyone — always pass requests with session/cart cookies.
Set-Cookieon every response — Varnish won’t cache them; fix the app or strip the header for anonymous pages.Vary: User-Agent— fragments the cache; normalise or remove.- Memory too small — objects are evicted constantly; watch
n_lru_nukedin varnishstat.
Related: website speed optimization and HTTP caching headers.