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-Cookie on 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_nuked in varnishstat.

Related: website speed optimization and HTTP caching headers.

  • Varnish
  • Caching
  • Performance