Back to tutorials
Tutorial

Varnish Cache Setup Tutorial (2026): Add Full-Page Caching in Front of Nginx/Apache on a VPS

Varnish cache setup tutorial for VPS hosting: install, configure, purge, and safely cache WordPress with real rules.

By Anurag Singh
Updated on Jul 15, 2026
Category: Tutorial
Share article
Varnish Cache Setup Tutorial (2026): Add Full-Page Caching in Front of Nginx/Apache on a VPS

Your VPS can generate HTML quickly—until every request forces PHP (or your app) to rebuild the page. This Varnish cache setup tutorial shows how to put Varnish in front of Nginx or Apache, cache what should be cached, and avoid common traps that break logins, carts, and admin areas.

The goal is simple. Reduce backend work, absorb traffic spikes, and keep your origin (PHP-FPM, Apache, or an Nginx upstream) from getting hammered.

By the end, you’ll have Varnish running as a service, a clean port layout, baseline cache rules you can live with, and a purge method that won’t become a security hole.

What you’ll build (ports, flow, and a quick sanity test)

Before you change anything, decide on the traffic flow. A boring, predictable port plan makes debugging much easier.

  • Varnish listens on :80 (public HTTP)
  • Nginx or Apache moves to :8080 (private backend)
  • HTTPS is terminated by your web server (recommended) or by a TLS proxy; Varnish stays HTTP-only

Request path (HTTP): client → Varnish (:80) → backend (:8080) → Varnish cache → client.

Quick sanity test after setup: a cached page should return X-Cache: HIT on the second request.

Prerequisites (Ubuntu/Debian or AlmaLinux/Rocky)

  • A VPS or dedicated server with root access
  • Nginx or Apache already serving a site
  • Enough RAM for cache (start small; 256MB–1GB is fine for many sites)
  • Ability to change firewall rules and ports

If you’re coming from shared hosting—or any environment where you can’t control ports—Varnish is a clean upgrade on a HostMyCode VPS.

You can move services safely and tune caching per site, instead of fighting platform limits.

Install Varnish (and confirm the version)

In 2026, most distributions ship Varnish 7.x. Check your installed version first.

Don’t paste VCL that targets older releases.

Ubuntu/Debian

sudo apt update
sudo apt install -y varnish
varnishd -V
systemctl status varnish --no-pager

AlmaLinux/Rocky

sudo dnf install -y varnish
varnishd -V
systemctl enable --now varnish
systemctl status varnish --no-pager

Leave Varnish running for now. Next, move your web server off port 80.

After that, you can give port 80 to Varnish.

Move your backend web server to port 8080 (Nginx or Apache)

This step causes most accidental downtime. Do it in order.

Change the listen port, test the config, reload/restart, then confirm the site answers on 127.0.0.1:8080.

Nginx: change listen 80 to listen 8080

Find your server blocks. They’re usually in /etc/nginx/sites-available/ (Debian/Ubuntu) or /etc/nginx/conf.d/ (RHEL family).

sudo grep -R "listen 80" -n /etc/nginx

Edit the relevant server block(s):

# example: /etc/nginx/sites-available/example.conf
server {
    listen 8080;
    listen [::]:8080;
    server_name example.com www.example.com;
    ...
}

Test and reload:

sudo nginx -t
sudo systemctl reload nginx

Verify locally:

curl -I http://127.0.0.1:8080/

Apache: change ports.conf and vhost listen

On Debian/Ubuntu, Apache typically uses /etc/apache2/ports.conf. On RHEL family, look under /etc/httpd/conf/.

# Debian/Ubuntu
sudo sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/ports.conf
sudo apachectl configtest
sudo systemctl restart apache2
curl -I http://127.0.0.1:8080/

If your vhost files hard-code <VirtualHost *:80>, update them to :8080 as well.

Pitfall: don’t expose the backend unless you truly need to. Bind to 127.0.0.1:8080 (or an internal IP) whenever possible.

Configure Varnish to listen on port 80 (and point to your backend)

Now you swap roles. Varnish becomes the public entry point on port 80.

Your web server stays on 8080 behind it.

Ubuntu/Debian: edit /etc/varnish/varnish.params or systemd override

On many Ubuntu/Debian installs, listen options live in /etc/varnish/varnish.params.

If your setup is systemd-only, use an override. Updates won’t overwrite your changes.

sudo systemctl edit varnish

Add:

[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd \
  -a :80 \
  -T 127.0.0.1:6082 \
  -f /etc/varnish/default.vcl \
  -s malloc,512m

Reload systemd and restart:

sudo systemctl daemon-reload
sudo systemctl restart varnish
sudo ss -lntp | egrep ':80|:8080|:6082'

RHEL family: edit /etc/varnish/varnish.params (common layout)

sudo grep -n "VARNISH_LISTEN_PORT" /etc/varnish/varnish.params
# set it to 80 if present
sudo systemctl restart varnish

Set the backend in /etc/varnish/default.vcl

sudo nano /etc/varnish/default.vcl

Minimal backend definition:

vcl 4.1;

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

Restart and test:

sudo varnishd -C -f /etc/varnish/default.vcl > /dev/null
sudo systemctl restart varnish
curl -I http://YOUR_SERVER_IP/

If you still seem to hit the backend directly, your web server is probably still bound to port 80.

Confirm with ss -lntp, fix the binding, and then continue.

Add practical caching rules (safe defaults for hosting)

Out of the box, Varnish won’t aggressively cache dynamic pages. Most hosting setups need a few guardrails.

Focus on three basics: bypass authenticated sessions, bypass admin paths, and stop tracking parameters from shredding your cache.

Paste the following into /etc/varnish/default.vcl under your backend block. Treat it as a baseline.

Then adjust paths for your application.

vcl 4.1;

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

sub vcl_recv {
  # Health check endpoint (optional)
  if (req.url == "/healthz") {
    return (pass);
  }

  # Don’t cache admin and login areas
  if (req.url ~ "^/(wp-admin|wp-login\\.php|administrator|user|checkout|cart)") {
    return (pass);
  }

  # Don’t cache requests with auth/session cookies
  if (req.http.Cookie ~ "(wordpress_logged_in|wp-postpass|PHPSESSID|session|auth)") {
    return (pass);
  }

  # Only cache GET/HEAD by default
  if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
  }

  # Normalize common tracking parameters to reduce cache fragmentation
  if (req.url ~ "(\\?|&)(utm_|gclid=|fbclid=)") {
    set req.url = regsuball(req.url, "(utm_[^&]+|gclid=[^&]+|fbclid=[^&]+)", "");
    set req.url = regsuball(req.url, "[\\?&]+$", "");
    set req.url = regsuball(req.url, "\\?&", "?");
  }

  return (hash);
}

sub vcl_backend_response {
  # Respect backend no-cache signals
  if (beresp.http.Cache-Control ~ "no-cache|no-store|private" || beresp.http.Pragma ~ "no-cache") {
    set beresp.uncacheable = true;
    set beresp.ttl = 0s;
    return (deliver);
  }

  # Default TTL for HTML if backend doesn't set one
  if (beresp.ttl <= 0s) {
    set beresp.ttl = 2m;
  }

  # Add a grace window to serve slightly stale content if backend is slow
  set beresp.grace = 5m;

  return (deliver);
}

sub vcl_deliver {
  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
  set resp.http.X-Cache-Hits = obj.hits;
}

Validate and restart:

sudo varnishd -C -f /etc/varnish/default.vcl > /dev/null
sudo systemctl restart varnish

Quick diagnostic:

curl -I http://example.com/
curl -I http://example.com/ | egrep 'X-Cache|X-Cache-Hits'

On a cacheable page, the second request is usually a HIT.

If it’s not, your backend may return Cache-Control: private. It may also be setting cookies on that path.

Make HTTPS work cleanly (recommended layout for real sites)

Most production sites in 2026 run HTTPS everywhere. Varnish does not terminate TLS on its own.

That leaves you with two realistic layouts:

  • Recommended: keep HTTPS on Nginx/Apache (port 443) and use Varnish mainly for HTTP or for internal caching flows.
  • Best performance option: terminate TLS on Nginx, then proxy HTTP to Varnish, which then talks to the backend. This requires a bit more wiring.

If you want full-page caching for logged-out users on HTTPS, use this pattern: Nginx listens on 443, decrypts, then forwards to Varnish on a private port (like 6081).

That’s a different port plan than the simple “Varnish on 80” layout above.

If you need a clean Let’s Encrypt setup on Nginx first, follow this SSL setup guide for Nginx. Then come back and place Varnish behind it.

Set up safe purging (so you can clear cache without flushing everything)

Editors will eventually ask for “clear cache” right after a post update. Plan for it.

Use a purge method that’s scoped and locked down.

Add this to your VCL (near vcl_recv):

acl purge {
  "127.0.0.1";
  "::1";
  # Add your office IP or VPN IP here, example:
  # "203.0.113.10";
}

sub vcl_recv {
  if (req.method == "PURGE") {
    if (!client.ip ~ purge) {
      return (synth(403, "Not allowed"));
    }
    return (purge);
  }

  # (keep the rest of your vcl_recv here)
}

Reload Varnish:

sudo varnishd -C -f /etc/varnish/default.vcl > /dev/null
sudo systemctl restart varnish

Purge a single URL from localhost:

curl -X PURGE http://127.0.0.1/

Tip: If you serve multiple hostnames, include the Host header so you purge the right object:

curl -X PURGE -H 'Host: example.com' http://127.0.0.1/

WordPress specifics: cache logged-out pages, avoid caching personalized content

WordPress caches well when you treat cookies as “personalization flags.” Two issues break naive setups most often.

They are preview links and WooCommerce sessions.

  • Cache only for logged-out users (your VCL already passes when wordpress_logged_in exists).
  • Don’t cache cart/checkout/account URLs. Your VCL blocks common paths; add any custom ones.
  • Watch for plugins that set cookies on every request. One unnecessary cookie can turn your whole site into a MISS factory.

If you’re also tuning WordPress backend performance, compare this approach with FastCGI caching in Nginx.

It’s often simpler on a single host.

Use our Nginx cache setup guide if you want caching without adding a separate Varnish layer.

Logging and troubleshooting (find out why you’re getting MISS)

Varnish gives you strong visibility. Start with its tools.

Then adjust rules based on what you observe.

Watch live requests

sudo varnishlog

If that’s too noisy, use:

sudo varnishncsa

Check cache hit ratio quickly

sudo varnishstat

Start with MAIN.cache_hit and MAIN.cache_miss.

If misses dominate, the backend may be marking responses private. Cookies may also be forcing PASS.

Common causes of “always MISS”

  • Backend sets cookies on every page (even for logged-out users). Fix the app/plugin or strip specific cookies carefully.
  • Cache-Control: private/no-store from the backend.
  • Vary: Cookie or aggressive Vary headers. Sometimes legitimate, sometimes not.
  • Multiple domains sharing one backend without proper host handling; ensure you pass the Host header (default behavior is fine).

If troubleshooting turns into “everything feels slow,” check disk pressure too.

This guide helps you isolate storage bottlenecks on a hosting node: disk I/O troubleshooting tutorial.

Firewall and security notes (don’t expose your backend)

Once Varnish is in front, treat the backend port as internal-only. If you bind to 127.0.0.1:8080, you’re already in good shape.

If you must bind to an interface, lock it down tightly.

  • Allow inbound 80/tcp to Varnish
  • Allow inbound 443/tcp to your TLS terminator (usually Nginx/Apache)
  • Block inbound 8080/tcp from the internet
  • Restrict 6082/tcp (Varnish admin) to localhost only

If you’re running WHM/cPanel on the same box, be cautious.

Don’t cache panel paths, and don’t accidentally leave management ports open.

For diagnostics, keep this firewall troubleshooting tutorial nearby.

Operational checklist: verify cache behavior before and after changes

  • Backend reachable: curl -I http://127.0.0.1:8080/
  • Varnish reachable: curl -I http://127.0.0.1/
  • Hits work: second request shows X-Cache: HIT
  • Logged-in bypass: login cookie triggers PASS
  • Admin paths bypass: /wp-admin/ always PASS
  • Purge restricted: PURGE from non-ACL IP returns 403
  • Backend not exposed: external scan can’t reach :8080

Where Varnish fits commercially (VPS vs dedicated vs shared)

Varnish makes sense when you control the network stack. That usually means a VPS or dedicated server.

On shared hosting, you typically can’t change ports or front the web server with a cache layer.

  • VPS: the best balance of price and control. You can tune cache and memory per site.
  • Managed VPS: a good fit if you want caching but don’t want to own patching and day-to-day service upkeep.
  • Dedicated server: a solid option for multi-site resellers or busy WooCommerce stores where you want a larger cache and predictable CPU.

If you’re moving off shared hosting specifically to get this level of control, do it in stages.

This walkthrough keeps downtime low: move from shared hosting to a VPS with a staging cutover.

Summary: stable full-page caching without breaking logins

Varnish behaves well when the design stays straightforward. Use one public entry point, a private backend port, and explicit rules that protect authenticated traffic.

Start with short TTLs. Watch varnishstat.

Then expand caching once you trust what’s being stored.

If you want a server where you can run Varnish, tune Nginx/Apache, and control email/DNS/SSL, a HostMyCode VPS is a practical baseline.

If you’d rather not manage updates and service restarts yourself, consider managed VPS hosting and spend your time on the application.

If you’re adding caching to absorb traffic spikes or stabilize response times, run Varnish on a VPS where you control ports, firewall rules, and memory allocation. HostMyCode offers VPS hosting for hands-on admins and managed VPS hosting if you want the platform maintained while you focus on tuning the site.

FAQ

Should I run Varnish if my site is already on Cloudflare?

Often, yes. Cloudflare can cache at the edge, but Varnish still reduces origin load for cache misses and non-edge traffic.

It also gives you tight, application-specific control over what gets cached and what never should.

Can Varnish cache HTTPS directly?

Not by itself. Terminate TLS on Nginx/Apache (or a TLS proxy) and forward decrypted HTTP to Varnish on a private port.

Why does my WordPress site never cache even when logged out?

The usual causes are cookies set on every request or a backend header like Cache-Control: private.

Check response headers with curl -I, then confirm what Varnish is doing with varnishlog.

What’s a safe starting cache size for a small VPS?

Start with -s malloc,256m or 512m and watch memory pressure. A smaller stable cache beats a larger cache that pushes the server into swap.

How do I clear cache for a single page after an update?

Use a restricted PURGE method (ACL-limited) and purge by URL. On multi-domain servers, include the Host header so you invalidate the correct object.