Varnish Cache
Install and configure Varnish Cache in front of Nginx to dramatically speed up WordPress and static websites
Varnish is a high-performance HTTP cache that sits in front of your web server. Cached pages are served directly from RAM without touching PHP or the database, handling thousands of requests per second that would otherwise overload the backend.
Architecture
Client → Varnish (:80/:443) → Nginx (:8080) → PHP-FPM → MySQL
↓
Cache HIT: served from RAM instantly
Cache MISS: forwarded to Nginx, response cachedInstallation
sudo apt update
sudo apt install varnish -y
varnishd -VMove Nginx to port 8080
Varnish will take port 80. First move Nginx:
sudo nano /etc/nginx/sites-enabled/default
# (or your site config)Change listen 80 → listen 8080 in all server blocks:
server {
listen 8080;
server_name example.com;
# ... rest of config
}Test and reload:
sudo nginx -t && sudo systemctl reload nginxConfigure Varnish to listen on port 80
sudo nano /etc/default/varnishSet the DAEMON_OPTS:
DAEMON_OPTS="-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-s malloc,256m"Also update the systemd unit (it overrides /etc/default/varnish):
sudo nano /lib/systemd/system/varnish.serviceFind the ExecStart line and change the port:
ExecStart=/usr/sbin/varnishd \
-j unix,user=vcache \
-F \
-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-s malloc,256mReload systemd and restart:
sudo systemctl daemon-reload
sudo systemctl restart varnishBasic VCL configuration
The Varnish Configuration Language (VCL) controls caching behavior. Edit /etc/varnish/default.vcl:
vcl 4.1;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
# Do not cache POST requests or authenticated sessions
if (req.method == "POST") {
return (pass);
}
# Do not cache logged-in WordPress users
if (req.http.Cookie ~ "wordpress_logged_in|woocommerce_cart_hash|wp_woocommerce_session") {
return (pass);
}
# Strip cookies from static files
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|woff|woff2|svg|ttf)$") {
unset req.http.Cookie;
return (hash);
}
# Remove UTM and tracking query strings from cache key
set req.url = regsuball(req.url, "(\?|&)(utm_source|utm_medium|utm_campaign|gclid|fbclid)=[^&]+", "");
return (hash);
}
sub vcl_backend_response {
# Cache static files for 1 day
if (bereq.url ~ "\.(css|js|png|gif|jpg|jpeg|ico|woff|woff2|svg|ttf)$") {
set beresp.ttl = 1d;
set beresp.http.Cache-Control = "public, max-age=86400";
unset beresp.http.Set-Cookie;
}
# Cache HTML pages for 2 minutes
if (beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 120s;
}
return (deliver);
}
sub vcl_deliver {
# Add header to indicate cache HIT or MISS
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
return (deliver);
}Apply:
sudo systemctl restart varnishWordPress-specific config
For WordPress sites, add cookie and URL handling to avoid caching admin pages and cart:
sub vcl_recv {
# Pass WordPress admin and login
if (req.url ~ "^/wp-(admin|login|cron|json)") {
return (pass);
}
# Pass WooCommerce dynamic pages
if (req.url ~ "^/(cart|checkout|my-account)") {
return (pass);
}
# Do not cache logged-in users
if (req.http.Cookie ~ "wordpress_logged_in") {
return (pass);
}
# Remove WordPress cookies from anonymous users
if (req.http.Cookie ~ "^$") {
unset req.http.Cookie;
}
}SSL with Nginx as SSL terminator
Varnish doesn't handle SSL natively. Keep Nginx on 443 for SSL, forwarding to Varnish on 80:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}Flow: Client → Nginx:443 (SSL) → Varnish:80 (cache) → Nginx:8080 (backend)
Purge cache
Purge the entire cache:
sudo varnishadm "ban req.url ~ /"Purge a specific URL:
sudo varnishadm "ban req.url == /my-page/"Purge by tag (requires Surrogate-Key headers from backend):
sudo varnishadm "ban obj.http.Surrogate-Key ~ post-123"Monitor cache performance
Live request log:
sudo varnishlogStatistics dashboard:
sudo varnishstatCache hit rate (aim for >80%):
sudo varnishstat -1 -f MAIN.cache_hit,MAIN.cache_miss | awk '{print $1, $2}'Useful commands
# Check Varnish status
sudo systemctl status varnish
# Test VCL syntax
sudo varnishd -C -f /etc/varnish/default.vcl
# Reload VCL without restart
sudo varnishadm vcl.load newconfig /etc/varnish/default.vcl
sudo varnishadm vcl.use newconfig
# View current config
sudo varnishadm vcl.show boot
# Adjust cache memory size
# Edit /lib/systemd/system/varnish.service: -s malloc,512m