When your web server starts choking under peak loads and response time (TTFB) exceeds 500 ms, it's time to implement an HTTP accelerator. Varnish Cache intercepts requests before the backend and serves cached responses from RAM. This reduces latency to 1–5 ms and the server can handle tens of thousands of requests per second. We are a team with 5 years of hands-on experience, having implemented Varnish for over 100 highload services. Proper configuration directly improves Core Web Vitals (LCP, INP) and can save up to 500,000 RUB per year on server infrastructure. A typical hit rate with correct VCL reaches 95–99%, and response time drops by a factor of 100 compared to direct backend requests.
Problems We Solve
- High backend load. Without caching, every request is processed by the application — database queries, rendering, business logic. Varnish offloads up to 90% of requests by serving cached pages directly.
- Slow dynamic pages. Even if a page generates in 200 ms, under 1000 concurrent requests latency spikes. Varnish with Edge Side Includes (ESI) caches the common parts while personal blocks load asynchronously. This cuts page generation time by 60–80%.
- Unstable performance under traffic peaks. Varnish acts as a buffer: if the backend is temporarily down, it serves stale copies (grace mode). This prevents outages and 503 errors.
How Varnish Cache Improves Core Web Vitals
Core Web Vitals are metrics that affect Google ranking. Varnish directly impacts Largest Contentful Paint (LCP) and Interaction to Next Paint (INP). By caching HTML pages, LCP drops from 2–3 seconds to 200–300 ms. INP improves because the server delivers content faster, reducing script delays. On one e-commerce platform, we raised the hit rate from 40% to 98% by rewriting the VCL to handle AJAX requests and set appropriate TTLs. This reduced backend load by 90% and cut page load times from 3 seconds to under 200 ms.
Grace Mode: Why It Matters
Grace mode allows Varnish to serve stale cache when the backend does not respond in time. As Varnish documentation states: Grace mode allows serving stale cache to prevent 503 errors. Setting beresp.grace = 6h gives the backend 6 hours to recover while Varnish continues to deliver old copies. This is critical for highload projects where every second of downtime has a cost.
When Varnish Is Not Needed
Varnish is ineffective for fully personalized content (e.g., account dashboards, online banking) where every request requires unique data. In such cases, application-level caching (Redis, Memcached) or a CDN is a better fit. However, even for those projects Varnish can cache static assets and shared page blocks.
Our VCL Configuration Approach
We start with a solid base VCL and then tailor it to your application. Below is a standard configuration we use as a starting point.
# Debian/Ubuntu
apt install varnish
# /etc/varnish/default.vcl
vcl 4.1;
import std;
backend default {
.host = "127.0.0.1";
.port = "8080";
.first_byte_timeout = 60s;
.connect_timeout = 5s;
.between_bytes_timeout = 10s;
}
# Multiple backends with load balancing
import directors;
backend app1 { .host = "10.0.0.1"; .port = "8080"; }
backend app2 { .host = "10.0.0.2"; .port = "8080"; }
sub vcl_init {
new cluster = directors.round_robin();
cluster.add_backend(app1);
cluster.add_backend(app2);
}
sub vcl_recv {
set req.backend_hint = cluster.backend();
# Normalize URL (remove trailing slash except root)
if (req.url ~ "^(/[^?]+)/$") {
set req.url = regsub(req.url, "/$", "");
}
# Don't cache admin panel
if (req.url ~ "^/admin") {
return (pass);
}
# Don't cache authenticated users (if content is personalized)
if (req.http.Authorization || req.http.Cookie ~ "session_id|auth_token") {
return (pass);
}
# Strip marketing parameters from cache key
set req.url = regsuball(req.url, "(\\?|&)(utm_source|utm_medium|utm_campaign|utm_content|fbclid|gclid)=[^&]+", "");
set req.url = regsub(req.url, "\\?&", "?");
set req.url = regsub(req.url, "\\?$", "");
# Normalize Accept-Encoding
if (req.http.Accept-Encoding) {
if (req.url ~ "\\.(gif|jpg|jpeg|png|webp|avif|ico|zip|gz|mp4)$") {
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "br") {
set req.http.Accept-Encoding = "br";
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
unset req.http.Accept-Encoding;
}
}
}
sub vcl_backend_response {
# Cache 404 briefly
if (beresp.status == 404) {
set beresp.ttl = 30s;
return (deliver);
}
# Cache only successful responses
if (beresp.status != 200 && beresp.status != 301 && beresp.status != 302) {
set beresp.uncacheable = true;
return (deliver);
}
# If backend didn't send Cache-Control, set defaults
if (!beresp.http.Cache-Control) {
if (bereq.url ~ "\\.(css|js|woff2|png|jpg|svg)$") {
set beresp.ttl = 30d;
} else {
set beresp.ttl = 5m;
}
}
# Grace period: serve stale cache on backend failure
set beresp.grace = 6h;
# Compression
if (beresp.http.content-type ~ "text/(html|css|javascript|xml|plain)" ||
beresp.http.content-type ~ "application/(json|javascript)") {
set beresp.do_gzip = true;
}
# Don't store Set-Cookie in cache
unset beresp.http.Set-Cookie;
}
sub vcl_deliver {
# Debug header (remove in production)
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT " + obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Hide internal headers from client
unset resp.http.X-Varnish;
unset resp.http.Via;
unset resp.http.X-Powered-By;
}
sub vcl_hit {
if (obj.ttl >= 0s) { return (deliver); }
# Grace: serve stale object
if (obj.ttl + obj.grace > 0s) { return (deliver); }
return (restart);
}
Cache Invalidation: PURGE, BAN, ESI
acl purge_acl {
"127.0.0.1";
"10.0.0.0"/8;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge_acl) {
return (synth(405, "Not allowed"));
}
return (purge);
}
if (req.method == "BAN") {
if (!client.ip ~ purge_acl) {
return (synth(405, "Not allowed"));
}
ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url);
return (synth(200, "Ban added"));
}
}
Usage from application:
# Invalidate specific URL
curl -X PURGE http://localhost/page/about
# Invalidate by pattern (BAN)
curl -X BAN -H "Host: company.com" http://localhost/category/
ESI (Edge Side Includes)
sub vcl_backend_response {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
set beresp.do_esi = true;
}
}
Our Work Process
- Application audit: analyze URLs, cookies, sessions, update frequency.
- VCL design: define caching rules, TTLs, grace, ESI requirements.
- Staging testing: measure hit rate, backend load, page speed.
- Deployment: set up systemd, memory allocation, monitoring.
- Monitoring & optimization: use varnishstat, Prometheus, iterate on VCL.
Application audit checklist before Varnish setup
- Analyze URL structure and parameters
- Identify dynamic vs static pages
- Check cookie and session usage
- Determine content update frequency
- Spot backend performance bottlenecks
Timeline and What's Included
Basic Varnish setup with standard VCL takes 1–2 working days. For projects with custom architecture, ESI, or complex invalidation rules, expect 2–3 days. Cost is determined after an audit; typical server resource savings of 30–40% reduce hosting expenses significantly.
What's Included in Our Service
- Audit of current architecture and bottleneck identification
- Custom VCL configuration tailored to your application
- Load balancing and grace mode setup
- PURGE/BAN invalidation and ESI implementation if needed
- Staging testing and performance measurement
- Production deployment with monitoring (Prometheus + Grafana)
- Documentation and team training
- Warranty support after deployment
Typical Configuration Mistakes
| Mistake | Solution |
|---|---|
| Ignoring URL normalization | Add trailing slash normalization in vcl_recv |
| Caching authenticated pages | Check cookies and Authorization header; return(pass) for personalized content |
| Too long TTL for dynamic content | Set reasonable TTL (1–5 minutes) for frequently updated pages |
| Content Type | TTL | Grace | ESI | Example URL |
|---|---|---|---|---|
| Static (CSS, JS, images) | 30 days | 12h | No | /static/css/app.css |
| Catalog pages | 5 minutes | 6h | Yes (for filters) | /catalog/phones/ |
| Personalized pages | 0 (pass) | - | No | /account/ |
For deeper VCL learning we recommend Varnish documentation.
Contact us to discuss your project and get a Varnish performance audit.







