Software & Configuration

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 cached

Installation

sudo apt update
sudo apt install varnish -y
varnishd -V

Move 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 80listen 8080 in all server blocks:

server {
    listen 8080;
    server_name example.com;
    # ... rest of config
}

Test and reload:

sudo nginx -t && sudo systemctl reload nginx

Configure Varnish to listen on port 80

sudo nano /etc/default/varnish

Set 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.service

Find 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,256m

Reload systemd and restart:

sudo systemctl daemon-reload
sudo systemctl restart varnish

Basic 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 varnish

WordPress-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 varnishlog

Statistics dashboard:

sudo varnishstat

Cache 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

On this page