Web Server Caching
Web server caching stores HTTP responses in a reverse proxy that sits in front of the application tier. NGINX, Varnish, and Apache Traffic Server intercept requests, check a local cache, and serve cached responses without involving the application at all. This drops application load dramatically, slashes latency for cacheable requests, and gives the application a graceful failure mode (serve stale when the app is down). It is the layer between the CDN and the application, governing how much read traffic ever reaches the app tier.
How it works
Web server caching (also called reverse proxy caching) places a cache inside the reverse proxy that fronts the application tier. When a request arrives, the proxy checks its local cache; on a hit, it serves the cached response directly, never touching the application. On a miss, it forwards to the application, caches the response, and serves it.
This sits between the CDN (which is geographically distributed, at edge POPs) and the application cache (Redis, in-process). It is the layer that absorbs cacheable traffic that slipped past the CDN — typically because the user is in a region with no nearby POP, or because the CDN evicted the entry, or because the request was not CDN-eligible (e.g., it carried an authorization header).
client → CDN edge → reverse proxy cache (NGINX/Varnish) → application tier → Redis → DBThe classic tools are NGINX (with the proxy_cache directive), Varnish (purpose-built for HTTP caching, configured via VCL — Varnish Configuration Language), and Apache Traffic Server. Cloud variants include AWS CloudFront edge caches (which double as both CDN and web-cache) and managed offerings like Cloudflare Cache Reserve.
The two big differences from a CDN: (a) the web server cache is in your data center, so latency to it is the same as to your app tier (single-digit ms), and (b) you have full programmatic control — VCL lets you write logic that no CDN would let you deploy (e.g., strip cookies, rewrite URLs, vary cache key by user segment).
VCL — the Varnish superpower.
Varnish Configuration Language (VCL) is what sets Varnish apart. VCL is a compiled DSL that lets you write arbitrary logic for every request and response — inspect headers, rewrite URLs, change cache keys, bypass the cache conditionally, purge by pattern, and more. NGINX has proxy_cache directives with similar (though less expressive) capabilities.
Typical VCL patterns:
- Strip cookies from static asset requests so that
Cookie: session=abcdoes not bust the cache for/static/app.js. - Vary cache key by user segment — cache a different version for logged-in vs logged-out users, or for users in different countries.
- Bypass cache for POST/PUT/DELETE — only cache idempotent requests.
- Grace mode — serve stale content when the origin is slow or down (
stale-while-revalidatebaked in). - Saint mode — temporarily blacklist an unhealthy origin, preventing cache misses from reaching it.
- PURGE — invalidate individual URLs by request from the application tier.
The key insight is that the web server cache is the most programmable cache layer. The CDN is at the edge but config-light (you mostly set headers and hope). The app cache is per-object but in your application code. The web server cache is in the data path and fully programmable, which is why Varnish is the favorite of high-traffic sites that need fine-grained caching logic.
Varnish's grace mode lets the proxy serve a stale cached response when the origin is slow or down — for a configurable period after the TTL expires. If your origin crashes, Varnish keeps serving stale content from cache, buying you time to fix the origin without users seeing errors. This is the web-server cache's most operationally valuable property: it is a circuit breaker and a degradation strategy in one. NGINX has the equivalent with proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504.
Cache keys — what counts as the same request.
The cache key determines whether two requests hit the same cached response. The default is (method, host, URL) — but this is rarely sufficient.
Common cache key considerations:
- Query parameters.
?utm_source=emailshould not bust the cache for a static page. VCL/NGINX config typically normalizes query params, stripping tracking params before computing the key. - Cookies. Logged-in users see different content, so cookies must be part of the key OR the response must not be cached. Static asset requests should have cookies stripped.
- Headers.
Accept-Encoding: gzipvs no-encoding produce different responses — the cache key must include encoding. So mustAccept-Languageif the response is localized. - Authorization. A request with
Authorization: Bearer ...should NOT be cached by default (it is user-specific). Either bypass the cache for authed requests, or vary the cache key by user.
The most common web-cache bug: caching a logged-in user's response under a shared key, then serving it to a different user. The fix is either (a) strip cookies and only cache truly public responses, or (b) include the user ID in the cache key. Most production VCLs do both: cache aggressively for cookieless requests, bypass for authed requests.
When to use web server caching:
- Any website serving public, cacheable HTTP responses. Default-on for most deployments.
- Sites with a heavy application tier that you want to shield from cacheable traffic.
- APIs with idempotent GET endpoints that return mostly-public data.
- When you need fine-grained caching logic (per-segment cache keys, conditional purges) that the CDN does not allow.
- When you need a graceful failure mode —
gracemode serves stale content while the app recovers.
When NOT to use web server caching:
- Personalized responses (per-user dashboards, real-time data) — the cache hit rate will be near zero, and you risk cross-user leakage.
- POST/PUT/DELETE responses — non-idempotent, must reach the app.
- Endpoints that require authentication and return user-specific data. Bypass the cache or use per-user keys.
- Low-traffic internal services where the app tier can handle all traffic directly. The proxy adds operational complexity.
A logged-in user's profile page gets cached by your Varnish server under the URL `/profile`. The next user to visit `/profile` sees the first user's profile. What went wrong, and what is the fix?
Pick one answer.
Your origin (application tier) goes down for 3 minutes. Users continue to see cached responses during the outage, with no errors. What feature made this possible, and why is it valuable?
Pick one answer.
Why is Varnish's VCL more powerful than NGINX's `proxy_cache` directives for complex caching logic?
Pick one answer.
Engineering mental model
Mental model. Think of Web Server Caching as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Web Server Caching mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing Web Server Caching, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
// Pseudocode
request = receive()
result = web_server_caching(request)
return result
// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?Back-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: Web Server Caching
Change the variables below and predict what breaks first in Web Server Caching. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on Web Server Caching, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using Web Server Caching. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using Web Server Caching?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses Web Server Caching, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose Web Server Caching, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
For Web Server Caching, reason in this order: what data is hottest, where can it live closer to the caller, what makes it stale, and what happens on a miss or cache failure. A cache is an optimization boundary, not the source of truth.
Numerical sanity check
A useful first-order model is cache_load = request_rate × (1 - hit_rate). If traffic is 20,000 req/s and the hit rate is 90%, roughly 2,000 req/s still reaches the origin before considering misses caused by expiration or eviction.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
You have a read-heavy Web Server Caching path. Traffic doubles overnight. What metric would you inspect first, and what would convince you to add another cache layer?
Pick one answer.
What you gain, what you pay
- +Drops application tier load 20-100x — one Varnish node absorbs what 20-100 app nodes could serve.
- +Sub-millisecond response on cache hits — far faster than the application tier.
- +Grace mode serves stale content during origin failure — built-in circuit breaker.
- +Full programmatic control via VCL — fine-grained caching logic no CDN allows.
- +In your data center — single-digit-ms latency to the app tier, full operator control.
- −Risk of cross-user data leakage if cache keys are not carefully designed.
- −In-memory storage means a restart loses the cache (warm-up period after restart).
- −Single point of failure unless deployed in HA pairs behind a load balancer.
- −Operational complexity — VCL is its own language to learn and maintain.
- −Does not help for non-cacheable traffic (POST, authed, personalized).
How this breaks in production
- Cross-user data leakage — caching user-specific responses under shared keys.
- Cache poisoning — caching a response that should not have been cached (e.g., an error page).
- Warm-up after restart — a fresh Varnish has an empty cache; origin sees a burst of misses.
- Cache key explosion — including too many headers/cookies in the key drops hit rate to zero.
- Stale content served past origin recovery if `grace` mode is too aggressive.
- Memory exhaustion — uncapped cache can grow until OOM; must configure LRU eviction.
Don't fall into these traps
- •Caching authenticated responses under URL-only keys — cross-user leakage.
- •Forgetting to strip tracking query params (`utm_*`) from the cache key — every variant busts the cache.
- •Not configuring `grace` mode — losing the most valuable failure-mode protection.
- •Treating Varnish as a black box — VCL is programmable; you should use it.
- •Restarting Varnish during peak traffic — the warm-up period can overwhelm the origin.
- •Single-instance deployment without HA — Varnish is a SPOF if not deployed in pairs.
Real systems using this
How real systems implement this
- Varnish at the BBC, NYT, Reddit — These high-traffic sites run Varnish in front of their CMS or app tier, configured via VCL with per-segment cache keys, query param normalization, and aggressive grace mode. One Varnish node absorbs 100,000+ req/sec of cacheable traffic.
- NGINX proxy_cache in standard deployments — NGINX's `proxy_cache` directive is the default for any NGINX-fronted application — Rails, Django, Spring Boot, Node. Simpler than Varnish, sufficient for most sites. Configured via `proxy_cache_path`, `proxy_cache_key`, `proxy_cache_valid`.
- Kong / Tyk API gateways — API gateways implement response caching at the web-server layer, with TTLs and keys configured per-route. This absorbs idempotent GET traffic before it reaches the upstream service.
- Cloudflare Cache Reserve / AWS CloudFront edge-to-shield — Managed web-server cache layers that combine CDN and origin-shield caching. Cloudflare Cache Reserve extends edge caching with durable storage; CloudFront's edge + shield pattern collapses misses before they reach the origin.
Practice saying it out loud
- Q1Design the caching layers for a high-traffic news site. Where does the web-server cache fit relative to the CDN and the app cache?
- Q2A user reports seeing another user's data on your site. Walk through every cache layer where the leak could have happened.
- Q3Your application tier goes down for 5 minutes but users see no errors. What made this possible, and how would you extend the window?
- Q4When would you choose Varnish over NGINX `proxy_cache`?
- Q5How do you invalidate a single URL in a web-server cache without restarting?
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
Database Caching