
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Slow dynamic applications frustrate users and inflate cloud bills by forcing expensive compute resources to regenerate identical content. Implementing reverse proxy and caching with Varnish solves this by serving cached responses from memory in microseconds, bypassing PHP or application logic entirely for repeat requests. If you are already running a standard LEMP stack, understanding where Varnish fits is critical before attempting complex optimizations like those in our guide on Laravel performance optimization techniques.
How does reverse proxy and caching with Varnish actually work?
Varnish operates as an HTTP accelerator designed specifically for heavy-load environments. Unlike general-purpose web servers, it stores cached objects entirely in virtual memory managed by the kernel, avoiding disk I/O bottlenecks. When a request arrives, Varnish computes a hash based on the URL and host header, then checks its storage. A hit returns the object immediately; a miss triggers a fetch from the defined backend.
This architecture makes reverse proxy and caching with Varnish exceptionally fast for read-heavy workloads like CMS platforms, e-commerce catalogs, and API gateways. However, it requires careful configuration because Varnish speaks only HTTP. It does not terminate TLS natively. In production, you almost always place Nginx or HAProxy in front to handle SSL termination, forwarding decrypted traffic to Varnish on port 6081. This separation of concerns keeps the caching layer simple and focused purely on acceleration.
A common mistake I see teams make is treating Varnish as a drop-in replacement for Nginx. It is not. Think of it as a specialized co-processor for HTTP delivery. Your existing web server configuration remains relevant, but now handles only uncached or private requests. For teams setting up fresh infrastructure, our LEMP stack setup guide covers the foundational Nginx layer that typically sits upstream.
How do you install and configure Varnish on Ubuntu 24.04?
Installation on modern Ubuntu systems uses the official packages. Avoid outdated tutorials referencing older init scripts; current versions use systemd exclusively.
- Install the package:
sudo apt update && sudo apt install varnish - Edit the systemd service to set memory allocation. Open
/etc/systemd/system/varnish.service.d/custom.conf:
The[Service] ExecStart= ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a :6081 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,1G-s malloc,1Gflag allocates 1GB of RAM for cache storage. Adjust based on available memory and content size. - Configure your backend in
/etc/varnish/default.vcl:
This tells Varnish where to find your application when a cache miss occurs.vcl 4.1; backend default { .host = "127.0.0.1"; .port = "8080"; .connect_timeout = 5s; .first_byte_timeout = 90s; .between_bytes_timeout = 2s; } - Reload systemd and restart:
sudo systemctl daemon-reload && sudo systemctl restart varnish
Verify operation with varnishlog -g request. You should see incoming requests and their cache status (hit, miss, pass). If everything shows "pass", your VCL is likely bypassing cache due to cookies or headers — addressed in the next section.
How do you write VCL to handle cookies and cache invalidation safely?
The default VCL is conservative. It passes (skips cache) for any request containing Cookie or Authorization headers. For dynamic apps, this means zero caching unless you explicitly normalize these headers. Here is a practical pattern for a typical CMS or Laravel app:
sub vcl_recv {
# Remove tracking cookies but preserve session auth
if (req.http.Cookie) {
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_ga|_gid|utm_[^=]+)=[^;]*", "");
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
}
# Bypass cache for admin/authenticated routes
if (req.url ~ "^/(admin|api|login|register)") {
return (pass);
}
# Normalize Accept-Encoding to improve hit rate
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
unset req.http.Accept-Encoding;
}
}
}
sub vcl_backend_response {
# Respect backend TTL but enforce minimum for public pages
if (beresp.ttl < 120s && !bereq.http.Authorization) {
set beresp.ttl = 120s;
}
# Don't cache error responses
if (beresp.status >= 400) {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
}
} Invalidation is equally important. Use BAN requests rather than PURGE for pattern-based invalidation. This lets you clear all product pages at once instead of individual URLs:
# From your deployment script or CI pipeline
curl -X BAN http://localhost:6081/ \
-H "X-Purge-Regex: /products/.*" \
-H "X-Ban-Key: secret-token" Always secure the BAN/PURGE interface. Never expose port 6082 publicly. Restrict access via firewall rules or VCL ACLs. Teams deploying via automated pipelines should integrate cache clearing into their release process, similar to the strategies covered in our zero-downtime deployment guide.
How does Varnish compare to Nginx FastCGI cache and Redis?
Choosing the right caching layer depends on your specific workload characteristics. Each tool has distinct strengths.
| Feature | Varnish | Nginx FastCGI Cache | Redis |
|---|---|---|---|
| Primary Use Case | Full-page HTTP caching | PHP/application response caching | Object/data fragment caching |
| Storage | RAM (malloc) | Disk (SSD recommended) | RAM + optional persistence |
| TLS Support | No (needs frontend proxy) | Yes (native) | No (application-level) |
| Configuration Language | VCL (stateful, powerful) | Directives (declarative) | Application code |
| Invalidation Granularity | BAN/PURGE by regex/tag | PURGE by key/wildcard | Key/pattern/delete |
| Best For | High-traffic public sites | Simpler stacks, no extra daemon | Session, query, computed data |
In practice, many high-performance stacks combine these. Varnish handles full-page delivery, Redis stores session and query results, and Nginx manages TLS plus static assets. The decision isn't either/or — it's about layering appropriately. If your traffic is under 1k RPS and mostly authenticated, Nginx cache alone may suffice. If you serve millions of public page views daily, Varnish's purpose-built HTTP state machine delivers measurably lower p99 latency.
How do you monitor Varnish cache hit rates and tune performance?
Blindly deploying Varnish without observability leads to silent failures. You must track hit rate, memory usage, and backend health continuously.
- Real-time stats:
varnishstat -1gives a snapshot. MonitorMAIN.cache_hitvsMAIN.cache_miss. A healthy public site should sustain >85% hit rate after warmup. - Prometheus integration: Use
varnish_exporterto expose metrics. Alert on hit rate dropping below threshold or backend errors spiking. Our Prometheus monitoring guide covers dashboard patterns applicable here. - Memory tuning: Watch
SMA.s0.g_allocandSMA.s0.g_space. If space consistently nears zero, increase malloc allocation or review TTL policies. Fragmentation can occur with highly variable object sizes; considermallocoverfilestorage for predictable workloads. - Log analysis:
varnishncsaoutputs combined log format. Pipe to your centralized logging stack to correlate cache behavior with user experience metrics. Low hit rates often trace back to unnormalized query strings or rogue cookies.
Performance tuning extends beyond Varnish itself. Ensure your backend sets appropriate Cache-Control headers. Varnish respects max-age and s-maxage; missing headers force fallback TTLs. Also verify TCP keepalives and connection pooling between Varnish and backend — each new connection adds latency on misses.
Implementing Reverse Proxy and Caching with Varnish in Production
Effective reverse proxy and caching with Varnish transforms application responsiveness and reduces infrastructure costs, but success depends on methodical implementation. Start with accurate backend definitions, normalize headers aggressively, validate cache behavior with real traffic patterns, and instrument everything before going live. Treat VCL as production code: version it, test it, review it. The payoff is a system that serves users faster while costing less to operate.
If you need help architecting a caching strategy tailored to your traffic profile or compliance requirements, reach out to discuss your infrastructure. I help teams build performant, audit-ready systems that scale without surprise.