Linux

Low Varnish Hit Rate? How to Find and Fix the Causes

Measure your real Varnish hit ratio, find the URLs that miss, and fix the cookies, query strings and headers that…

1 November 2026  ·  4 min read  ·  Anand

Low Varnish Hit Rate? How to Find and Fix the Causes

Varnish can answer a request from memory in well under a millisecond, but only for requests it is allowed to cache. Many installations we review run at a hit rate far below what the site could achieve, so most traffic still reaches PHP and the database. The cause is rarely Varnish itself. It is usually cookies, tracking parameters or headers that make every request look unique or uncacheable. This guide shows how to measure your hit rate and fix the most common causes without caching anything that must stay private.

Step 1: measure the hit ratio

varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss -f MAIN.cache_hitpass -f MAIN.s_pass

The hit ratio is cache_hit / (cache_hit + cache_miss). Passes (s_pass) are requests Varnish was told not to cache at all, so a large pass count is often the bigger problem. Take two readings a few minutes apart and compare the difference, because the counters run from the last restart.

Step 2: see which URLs miss

Every miss and pass becomes a backend request. This shows the URLs Varnish fetches most often, live:

sudo varnishtop -i BereqURL

If the same page appears many times with different query strings, or pages you expect to be cached keep appearing, the sections below explain why.

Cause 1: cookies

Varnish’s built-in logic does not cache a request that carries a Cookie header, or a response that sets one. Analytics and consent tools set cookies on every visitor, so without your own VCL almost nothing is cached. The safe approach is to remove cookies only where they cannot matter, for example on static files:

sub vcl_recv {
    if (req.url ~ "\.(css|js|png|jpe?g|gif|svg|webp|avif|woff2?)(\?.*)?$") {
        unset req.http.Cookie;
    }
}

For pages, keep only the cookies your application actually needs (the session, cart or login cookie) and remove the rest. Requests that still carry a session cookie must keep bypassing the cache, so logged-in users, carts and checkout are never shared between visitors.

Cause 2: tracking parameters in the URL

Links from email campaigns and ads add utm_source, gclid or fbclid to the URL. Varnish treats every variation as a different page, so each campaign click is a miss. Remove these parameters before the cache lookup:

sub vcl_recv {
    if (req.url ~ "[?&](utm_[a-z]+|gclid|fbclid)=") {
        set req.url = regsuball(req.url, "(utm_[a-z]+|gclid|fbclid)=[^&]*&?", "");
        set req.url = regsub(req.url, "[?&]$", "");
    }
}

Your analytics still works, because the browser keeps the full URL and the analytics script reads it there.

Cause 3: Vary headers that split the cache

A Vary: User-Agent header tells Varnish to store a separate copy for every browser version, which can mean thousands of copies of one page. Check with curl -sI https://example.com/ | grep -i vary. Vary on Accept-Encoding is normal and Varnish handles it efficiently. If a plugin adds Vary on User-Agent or Cookie, remove it at the application or in vcl_backend_response once you are sure the page is the same for everyone.

Cause 4: the application says do not cache

Responses with Cache-Control: private, no-store or max-age=0 are not cached for long, or at all. Many CMSs send these by default. Fix the headers in the application where you can, or set a TTL in vcl_backend_response for page types you have confirmed are public. Use s-maxage to give Varnish a longer TTL than browsers.

Cause 5: the cache is too small or purged too often

A steadily rising MAIN.n_lru_nuked counter means objects are evicted to make room, so they miss again later. Increase the storage size if the server has memory to spare. Also check what your CMS purges: a plugin that bans the whole cache on every comment or stock change will keep your hit rate low no matter how good the VCL is. Tag-based purging that removes only the affected pages fixes this.

Make misses cheaper with grace

Even a perfect setup has misses when content expires. With a grace period, Varnish serves the slightly stale copy instantly and refreshes it in the background, so visitors do not wait for the backend:

sub vcl_backend_response {
    set beresp.grace = 6h;
}

Check before and after

Measure the hit ratio and the backend request rate before each change and a day after it, and test logged-in, cart and checkout pages every time. A higher hit rate is only a win if no visitor ever sees content meant for someone else.

When to get help

TechProvidence engineers audit and tune Varnish for WordPress, Magento and custom applications as part of our Varnish support and cache tuning service. If you also use a CDN, the same cache key problems apply at the edge, and our CDN management team tunes both layers together. Contact us for a complimentary Varnish review.

Running into something similar?

We look after Linux servers, control panels and virtualization for businesses every day. If an update, vulnerability or outage here affects you, we can help.