If you're running WordPress or WooCommerce on a plain VPS with Nginx or Apache — no cPanel, no LiteSpeed — you've probably already installed a caching plugin and maybe turned on Redis for object caching. Pages still feel slower than they should, especially under a traffic spike. The piece that's usually missing is a full-page cache sitting in front of your web server, and on a VPS that's a job for Varnish. Here's how to set it up without breaking SSL, login sessions, or your WooCommerce cart.

What Varnish Actually Does (and Doesn't)

Varnish is an HTTP accelerator. It sits between the internet and your web server, listens on the port your visitors actually hit, and serves cached copies of pages straight from RAM — no PHP, no database query, no Nginx even waking up. For a mostly-static blog or a WooCommerce catalog page, that's the difference between a 400ms response and a 15ms one.

What it doesn't do is cache logged-in sessions, cart pages, or checkout by default — and it shouldn't. Get that wrong and you'll show one customer's cart total to another customer, which is a much worse problem than a slow page.

Before You Start

  • Root or sudo access via SSH to your VPS
  • Nginx (or Apache) already running your site on port 80/443
  • A WordPress site with a caching plugin like W3 Total Cache or WP Super Cache (optional, but Varnish plays nicer when it can see cache headers)
  • Comfortable moving your web server off port 80 temporarily — Varnish is going to take that slot

Step 1: Install Varnish

On Ubuntu/Debian:

sudo apt update
sudo apt install varnish

On AlmaLinux/Rocky:

sudo dnf install epel-release
sudo dnf install varnish

Check the version — this guide assumes Varnish 6.x or 7.x, which is what most package repos ship today:

varnishd -V

Step 2: Move Nginx to a Backend Port

Varnish needs port 80 for itself, so Nginx has to move. Edit your site's server block and change the listen directive:

server {
    listen 127.0.0.1:8080;
    server_name example.com;
    ...
}

Restart Nginx and confirm it's actually answering on the new port before you touch Varnish:

sudo systemctl restart nginx
curl -I http://127.0.0.1:8080

If that curl doesn't return a 200, stop here and fix it — Varnish will just inherit the same failure and mask the real error behind a generic 503.

Step 3: Point Varnish at Nginx

Edit the default VCL, usually at /etc/varnish/default.vcl:

vcl 4.1;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

Then tell the Varnish service to listen on port 80. On systemd-based distros, edit the service override:

sudo systemctl edit varnish

Add:

[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -a :80 -a localhost:8443,PROXY -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,256m

Reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart varnish
sudo systemctl status varnish

Load your site in a browser. If it comes up normally, check the response headers for X-Varnish or Via: varnish — that confirms traffic is actually routing through it and not just hitting Nginx directly out of habit from a stale DNS cache on your own machine.

Step 4: Handle SSL

Varnish itself doesn't do TLS termination well out of the box. The standard setup is to put something in front of Varnish to terminate SSL — either Nginx again (a thin instance on 443 that proxies to Varnish on 80, which proxies to your real Nginx on 8080), or Hitch, which is built for exactly this and pairs naturally with Varnish. If you're already running Certbot for Let's Encrypt, keep that certificate — you're just changing which process presents it.

A common layout that avoids a triple-hop mess:

LayerPortJob
Nginx (SSL front)443Terminates HTTPS, proxies plain HTTP to Varnish
Varnish80Full-page cache
Nginx (backend)8080Runs PHP-FPM, serves the actual site

Step 5: Don't Cache Logged-In Users or Carts

This is the part people skip and then get a support ticket about. Add this to your VCL's vcl_recv so WordPress admin, logged-in sessions, and WooCommerce carts bypass the cache entirely:

if (req.url ~ "^/wp-admin" || req.url ~ "^/wp-login.php" ||
    req.http.Cookie ~ "wordpress_logged_in" ||
    req.http.Cookie ~ "woocommerce_items_in_cart") {
    return (pass);
}

Restart Varnish after any VCL change — it won't reload automatically:

sudo varnishadm vcl.load new /etc/varnish/default.vcl
sudo varnishadm vcl.use new

Step 6: Purge on Update

Without a purge mechanism, you'll publish a post or update a price and the old version keeps serving for hours. Install a plugin like Varnish HTTP Purge (works with most VCL setups out of the box) so WordPress sends a PURGE request whenever content changes. Test it by editing a published page and confirming the change shows up immediately, not after the TTL expires.

Common Problems

SymptomLikely CauseFix
503 Service Unavailable on every pageVarnish can't reach the backend on 8080Confirm Nginx is actually listening there with curl -I http://127.0.0.1:8080; check firewall rules aren't blocking loopback
Logged-in admin sees cached front-end contentCookie bypass rule missing or misspelledRe-check the vcl_recv pass rule for the exact cookie name your session plugin sets
WooCommerce cart count wrong for some visitorsCart cookie isn't in the bypass listAdd woocommerce_items_in_cart and woocommerce_cart_hash to the pass condition
SSL padlock missing or mixed content warningVarnish rewriting or dropping the scheme on redirectsMake sure your SSL-terminating layer sets X-Forwarded-Proto and WordPress's site URL uses https consistently
Content edits don't show upNo purge hook wired to WordPress savesInstall a Varnish purge plugin or lower the default TTL while you debug

Prevention

Once it's stable, don't leave the VCL as a black box. Keep a copy of default.vcl in version control (even a simple git repo on the VPS itself), so a bad edit is one git diff away from being understood instead of a mystery six months later. Set a sane default TTL — an hour is reasonable for most blogs — rather than caching forever and relying only on purge events. And if you're running WooCommerce, load-test checkout specifically after any VCL change; that's the page where a caching mistake costs you a sale, not just a slow load.

Frequently Asked Questions

Do I still need a WordPress caching plugin if I'm running Varnish?

Yes, though its job changes. Varnish handles full-page HTTP caching; a plugin like WP Super Cache or W3 Total Cache still helps with browser caching headers, minification, and generating the static HTML Varnish serves. They complement each other rather than duplicate work.

Can I run Varnish and Redis object caching together?

Yes, and it's a good combination. Varnish caches the full rendered page for anonymous visitors; Redis speeds up the PHP/database layer for everything Varnish has to pass through — logged-in users, cart pages, admin screens. They solve different problems.

Why does my site show a 503 error right after I restart Varnish?

Varnish usually throws a 503 when it can't reach the backend defined in your VCL. Double-check the backend's host and port match where Nginx (or Apache) is actually listening, and confirm nothing in your firewall is blocking connections to that local port.

Is Varnish worth it on a small VPS with modest traffic?

If your site is mostly static content — blog posts, a product catalog, marketing pages — even a small VPS benefits, since Varnish serves those requests from memory without touching PHP at all. If your site is almost entirely personalized or logged-in traffic (a dashboard app, for instance), the gains are much smaller and may not be worth the added complexity.

Does Varnish work with cPanel?

Not natively — cPanel/WHM doesn't manage Varnish out of the box, and stock cPanel setups already have Apache or LiteSpeed handling caching differently (LSCache, for one). Varnish is really a fit for unmanaged VPS setups running Nginx or Apache directly, which is the scenario this guide covers.