How to Set Up Varnish Cache on a VPS: Speed Up WordPress and High-Traffic Sites

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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!