How to Set Up Varnish Cache on a VPS: Speed Up WordPress and High-Traffic Sites
Varnish Cache is an HTTP accelerator that sits in front of your web server and serves cached responses from RAM. A WordPress page that takes 300ms to generate from PHP takes under 1ms when served from Varnish’s memory cache. Under traffic spikes that would overwhelm PHP-FPM, Varnish serves thousands of requests per second from cache while PHP handles only cache misses and logged-in user requests.
Architecture: Nginx + Varnish Stack
Internet → Nginx (port 443, SSL termination)
→ Varnish (port 6081, cache)
→ Nginx backend (port 8080)
→ PHP-FPM
→ WordPress + MariaDB
Nginx handles SSL because Varnish does not natively support HTTPS. Varnish handles caching. The backend Nginx serves PHP requests for cache misses.
Step 1: Install Varnish
sudo apt update && sudo apt install -y varnish
sudo systemctl enable varnish
Step 2: Reconfigure Nginx Backend to Port 8080
sudo nano /etc/nginx/sites-available/wordpress-backend
server {
listen 127.0.0.1:8080;
server_name yourdomain.com www.yourdomain.com;
root /var/www/wordpress;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
}
location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg|woff2)$ {
expires 1y;
access_log off;
}
}
sudo ln -s /etc/nginx/sites-available/wordpress-backend /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 3: Configure Varnish Startup Options
sudo nano /etc/default/varnish
DAEMON_OPTS="-a :6081 \
-T localhost:6082 \
-f /etc/varnish/wordpress.vcl \
-S /etc/varnish/secret \
-s malloc,256m"
Step 4: Write WordPress VCL Configuration
sudo nano /etc/varnish/wordpress.vcl
vcl 4.1;
backend default {
.host = "127.0.0.1";
.port = "8080";
.first_byte_timeout = 60s;
.connect_timeout = 5s;
}
acl purge {
"localhost";
"127.0.0.1";
}
sub vcl_recv {
# Pass logged-in WordPress users directly to backend (no caching)
if (req.http.Cookie ~ "wordpress_logged_in|wp-settings") {
return(pass);
}
# Pass POST requests and WP admin directly to backend
if (req.method == "POST" || req.url ~ "^/wp-(admin|login|cron)") {
return(pass);
}
# Handle cache purge requests from WordPress plugins
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return(synth(405, "Purge not allowed from this IP"));
}
return(purge);
}
# Remove cookies from static file requests (allows caching)
if (req.url ~ "\.(jpg|jpeg|png|gif|ico|css|js|woff2|svg|webp)(\?.*)?$") {
unset req.http.Cookie;
}
# Remove tracking parameters that create duplicate cache keys
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|gclid|fbclid)") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|gclid|fbclid)=[^&]+", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|gclid|fbclid)=[^&]+&", "?");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|gclid|fbclid)=[^&]+$", "");
}
}
sub vcl_backend_response {
# Cache HTML pages for 1 hour (WordPress content)
if (beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 1h;
set beresp.grace = 30m; # Serve stale while refreshing
}
# Cache static assets for 1 year
if (bereq.url ~ "\.(jpg|jpeg|png|gif|ico|css|js|woff2|svg|webp)$") {
set beresp.ttl = 365d;
unset beresp.http.Set-Cookie;
}
# Don't cache responses with Set-Cookie header
if (beresp.http.Set-Cookie) {
set beresp.uncacheable = true;
return(deliver);
}
}
sub vcl_deliver {
# Add debug header showing cache status
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
}
sudo systemctl restart varnish
Step 5: Nginx Frontend — SSL Termination into Varnish
sudo nano /etc/nginx/sites-available/wordpress-frontend
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://127.0.0.1:6081;
proxy_http_version 1.1;
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;
proxy_read_timeout 300s;
}
}
sudo ln -s /etc/nginx/sites-available/wordpress-frontend /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 6: Configure WordPress for Varnish
Install the Proxy Cache Purge plugin in WordPress. Configure it: Settings → Proxy Cache Purge → IP: 127.0.0.1, Port: 6081. The plugin automatically purges Varnish cache when posts are published or updated.
Add to wp-config.php above the line /* That's all, stop editing! */:
define('WP_HOME', 'https://yourdomain.com');
define('WP_SITEURL', 'https://yourdomain.com');
define('FORCE_SSL_ADMIN', true);
$_SERVER['HTTPS'] = 'on';
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
Testing and Benchmarking
# Verify cache HIT/MISS headers
curl -I https://yourdomain.com | grep X-Cache
# First request: X-Cache: MISS
# Second request: X-Cache: HIT
# Benchmark: before and after Varnish
ab -n 500 -c 20 https://yourdomain.com/
# Typical improvements:
# Without Varnish: ~50 req/sec, ~200ms average
# With Varnish: ~5,000 req/sec, <2ms average
# Manual cache purge (all pages)
curl -X PURGE -H "Host: yourdomain.com" http://127.0.0.1:6081/
Getting Started
Varnish’s in-memory cache requires RAM allocated at startup (malloc,256m in the example). A 2 GB VPS allocating 256 MB to Varnish leaves sufficient RAM for PHP-FPM, MariaDB, and Nginx. Ubuntu VPS plans at VPS.DO with NVMe storage provide fast underlying I/O for cache misses while Varnish serves hits from RAM at microsecond latency.
Conclusion
Varnish transforms a WordPress site from a PHP application that struggles under traffic spikes into a high-throughput cached content server that serves thousands of requests per second from RAM. The Nginx + Varnish + Nginx backend stack handles SSL, caches anonymous user pages, and bypasses the cache for logged-in users and admin requests — the best of all worlds for WordPress performance at scale.