HTTP caching in 2026: revisit Varnish and Vinyl Cache, Apache Traffic Server and NGINX before you put another cache in front of your app
Most teams add an HTTP cache on a bad day. The home page is slow during a traffic spike, the database is at 100% CPU, and somebody says “let us just put Varnish in front”. Two hours later the site is fast again. Two weeks later a reader complains that the page shows another user’s name, or an editor asks why a corrected headline is still visible after twenty minutes, or the cache server restarts and the origin falls over because every object is a miss at the same time.
None of these problems is about the cache being slow. They are about the cache doing exactly what it was told, and nobody having written down what it was told. Which part of the request makes up the cache key? How long is a response fresh, and what happens after that? Who is allowed to remove an object, and how quickly? What happens when the origin is down?
This post revisits three mature tools that answer those questions in front of an origin: Varnish Cache, now split into Vinyl Cache and a new Varnish Cache, Apache Traffic Server (ATS) and the NGINX proxy cache, with a brief look at Squid, Souin with the Caddy cache handler, and the HAProxy cache. All three serve cached responses far faster than your application can build them, so the goal is not to find the fastest. It is to understand each caching model, the Varnish naming change, and the habits that stop a cache from serving the wrong thing.
One piece of news first, because it changes how you search, install and even name things. In 2025 the open source Varnish Cache project renamed itself Vinyl Cache after a trademark disagreement with Varnish Software, and since March 2026 the same maintainers ship the daemon as vinyld. Varnish Software, meanwhile, relaunched a separate, company governed project that keeps the name Varnish Cache. So in late 2026, “install Varnish” can mean two different things. We untangle this below.
This post stays on caching. For TLS, routing, load balancing and connection handling read reverse proxies in 2026 first. An HTTP cache is a different layer from an application data cache; for that, see how Redis helps to increase service performance and Redis, Valkey and Dragonfly: revisit the choice. If you plan to prove that the cache helps, read load testing in 2026. If you also want request filtering at the edge, that belongs to web application firewalls in 2026, and certificates for the TLS layer in front of the cache are covered in TLS certificate automation in 2026. For gateways that sit in front of APIs rather than pages, see API gateways in 2026.
A scenario to keep us honest
We will use one invented company through the post. Suryanagari Media is an invented example, not a real business. It is a regional news publisher based in Jodhpur, running a Hindi and English news website, a mobile app and a daily e-paper for western Rajasthan. The tech team has 9 people, led by an engineer called Devika.
What they run:
- A PHP content management system on two application VMs, with MySQL and a Redis object cache.
- A small Node.js API that serves the mobile app (article lists, article bodies, live score and results widgets).
- Images and e-paper PDFs on S3 compatible object storage, behind the same domain.
- One NGINX VM in front, terminating TLS with certificates from certbot and proxying to the two application VMs.
- A paid CDN only for images, not for HTML.
How caching works today:
- Browsers cache static assets for a week, thanks to versioned file names.
- HTML pages and API responses are not cached anywhere. Every page view hits PHP and usually MySQL.
- Editors fix a headline and the change is live instantly, because there is no cache to clear.
What triggered the review: on a recent counting day for a by-election, traffic on the results page rose to about twelve times normal within twenty minutes. Both application VMs hit 100% CPU, the site returned errors for about 25 minutes, and readers moved to a competitor. A junior engineer had tried NGINX caching once before, a year ago, but turned it off within a day because a logged-in reader’s name appeared in the header for other readers. Since then the team has believed that “caching is risky for a news site”.
Devika’s questions: can we cache HTML safely, can editors still correct a story within a minute, what do readers see when the origin is down, and is the NGINX we already run enough?
The short version
- An HTTP cache is a policy engine, not just a speed booster. Before choosing a tool, write down your cache key, your freshness rules (TTL), what you serve when content is stale or the origin is failing, how you invalidate, and what must never be cached (personal pages, responses with
Set-Cookie). - Vinyl Cache (formerly Varnish Cache) gives the most control over caching policy through VCL, the richest invalidation model (purge, ban, grace), Edge Side Includes and an excellent logging tool. No built-in TLS. 9.1 became the new LTS in October 2026. BSD 2-clause.
- Varnish Cache from Varnish Software is now a separate, company governed distribution based on Vinyl, adding in-process TLS and Kubernetes packaging. Also BSD 2-clause. Choose between the two on purpose.
- Apache Traffic Server is built for large disk caches and CDN style hierarchies, with native TLS and deep plugins. It is the most work to operate, and two 2026 advisories fixed 49 CVEs. Apache 2.0, ASF governed.
- NGINX proxy cache is probably already in your stack. Locking, stale serving, background refresh and revalidation are solid; purge is commercial only and observability is thin. BSD 2-clause, F5.
- Squid, Souin with Caddy, and the HAProxy cache fit narrower cases.
- For Suryanagari, the plan we arrive at is: start with a short “microcache” in the NGINX they already run, with request locking and stale serving, cookies handled on purpose, and only anonymous traffic cached. Add Vinyl Cache 9.1 behind NGINX later, only if editors need instant purges and section level invalidation that NGINX open source does not give them.
Where things stand on 31 October 2026
Vinyl Cache (the former Varnish Cache open source project)
- Name change. On 15 September 2025, with the 8.0.0 release, Poul-Henning Kamp, the original author, announced a voluntary association under Danish law and a new name, because Varnish Software’s position was that nobody could use “Varnish Cache” without its permission. The association cannot hold money; its first general assembly met on 23 February 2026.
- Vinyl Cache 9.0.0, 16 March 2026. First release under the new name.
varnishdbecomesvinyld, the tools becomevinyllog,vinylstat,vinylncsaandvinyladm, the default VCL directory moves to/etc/vinyl-cache, the Unix users and theServer,ViaandX-Varnishheaders are renamed (the last becomesX-Vinyl). The upgrade notes say VCL needs changes only in rare cases, but scripts, monitoring and log parsers will notice. - Where the code lives. The project left GitHub. The source, issues and pull requests are on a self hosted Forgejo instance at code.vinyl-cache.org, and the old GitHub organisation is archived. The homepage is vinyl-cache.org.
- 9.1.0, 16 September 2026. A release focused on performance, stricter HTTP/1 message framing (conflicting
Content-LengthandTransfer-Encodinghandling, refusingTransfer-Encodingon HTTP/1.0 requests), new ban variables to remove hit-for-miss and hit-for-pass objects,req0.*variables to read the request as originally received, per response ESI switches, and the new HTTPQUERYmethod passed by default. - New LTS. On 29 October 2026 the maintainers named 9.1 the new long term support branch and set end of life for the old 6.0 LTS on 15 April 2027, nine years after 6.0.0. A 9.1.1 bug fix release is announced as coming soon. The 8.0 series is no longer supported.
- Security record in 2026. Three advisories: VSV00018 (absolute form URL parsing, fixed in 9.0.0, 8.0.1 and 6.0.17 on 16 March), VSV00019 (HTTP/2 parsing, fixed in 9.0.1, 8.0.2 and 6.0.18 on 11 April) and VSV00020 (a workspace overflow in the VCL
.upper()and.lower()string methods, a remote denial of service if your VCL uses them, fixed in 9.1.0 and 9.0.2 on 16 September). In 2025 there were three more: an HTTP/1 client side desync, a request smuggling issue, and the HTTP/2 “MadeYouReset” attack. - Licence and governance. BSD 2-clause. The maintainer roles are held by Poul-Henning Kamp, Walid Boudebouda and Nils Goroll, with one seat by long standing agreement nominated by Varnish Software. The association is designed so that it cannot hold or handle money.
Varnish Cache (the new project from Varnish Software)
- Varnish Software created the varnish/varnish repository on GitHub in November 2025 and keeps the “Varnish Cache” name under its trademark policy. Its site calls it a distribution of Vinyl Cache with long term support, extra modules and tooling.
- The visible differences are in-process TLS, official container images and a Helm chart. Licence: BSD 2-clause.
- Version numbers follow Vinyl’s series: varnish-9.1.0 was tagged on 21 September 2026 (UTC) and 9.0.4 on 16 September with the VSV00020 fix.
- The Vinyl maintainers describe it as a new downstream project with different governance and maintainers employed by Varnish Software. Two board members, in a section marked as their own opinion, compare it to MySQL and MariaDB.
- Varnish Software quotes 1.5 Tbps per server for Varnish Enterprise, measured by Intel and Varnish Software. That is the vendor’s own run, a sign of the ceiling, not a promise for your origin.
Apache Traffic Server
- Two supported lines: 10.x (latest 10.2.1) and the 9.2 LTS line (latest 9.2.16).
- 10.2.0, 17 August 2026. 1,039 commits from 44 contributors. Caching highlights: an opt-in shared memory cache directory so that a restart can attach to the existing index in milliseconds instead of rebuilding it from disk (the docs say the rebuild takes minutes on a large cache);
traffic_ctl cache clearfor a logical full purge by bumping the cache generation; support for RFC 9213 targeted cache control headers such asCDN-Cache-Control; S3-FIFO tuning for the RAM cache; a Prometheus friendly output from thestats_over_httpplugin; exponential back-off for origin connect retries; and a newckhlog field with the cache key hash. - Security in 2026. The July advisory (9.2.15 and 10.1.4, 28 July) fixed 34 CVEs, 9 rated important and 25 moderate. The October advisory (9.2.16, 10.1.5 and 10.2.1, 20 October) fixed 15 more, 2 critical, 8 important, 5 moderate. Both critical ones were in experimental plugins (
magickandwasm). Among the important ones are two request smuggling issues aroundExpect: 100-continueand request bodies, a malformed 302 response that could be stored and served (cache poisoning), and TLS session reuse problems with client certificates. - Licence and governance. Apache 2.0, an Apache Software Foundation top level project. The code started as Inktomi Traffic Server, which Yahoo later open sourced through the ASF in 2009.
NGINX
- Stable line 1.30, first released on 14 April 2026, now at 1.30.6 (14 October 2026). Mainline is 1.31.8 (21 October 2026).
- Security in 2026. The official advisory page lists 26 CVE identifiers for 2026, mostly rated medium, with two rated major (a buffer overflow with
mapand regex, and an HTTP/3 use-after-free in early 1.31 builds). A few touch features commonly used with caching: a memory disclosure in the slice module (CVE-2026-60005) and use-after-free issues in the SSI module (CVE-2026-56434, CVE-2026-97205). The 1.31.7 changelog also fixes a crash when anX-Accel-Redirectheader was cached and then served. - Cache feature status. The core cache directives have been stable for years;
proxy_cache_purgeis documented as part of the commercial subscription. - Licence and governance. BSD 2-clause, developed by F5, which sells NGINX Plus with extra cache features.
Around them
- Squid 7.8 (30 October 2026, GPLv2), after several 2026 advisories including a high rated request smuggling fix in September.
- Souin v1.7.9 (16 September 2026, MIT) and the Caddy cache handler v0.17.0 (17 September 2026, Apache 2.0).
- HAProxy 3.4 LTS, now 3.4.7 (14 October 2026), with its small RAM cache.
The first questions to ask
Before comparing tools, answer these questions in writing. Most caching incidents come from skipping one of them.
- What is the cache key? By default it is roughly scheme, host and URL. Does a query string like
?utm_source=whatsappcreate a new copy of the home page? Does the mobile app send a header that changes the response? - Who must never get a cached response? Logged-in users, admin pages, checkout, anything personal. How will the cache know? Usually by a cookie or an
Authorizationheader. - How fresh must each kind of content be? A results widget might need 5 seconds. An article page might be fine at 60 seconds. An e-paper PDF from last week never changes.
- What do you serve while refreshing, and when the origin fails? Stale for a few seconds while a background fetch runs? Stale for an hour if the origin is down?
- How do you invalidate? One URL at a time, a whole section, everything with a given tag, or simply wait for the TTL?
- What happens on a cold start? If the cache restarts empty, can the origin survive the burst of misses?
- Where is TLS terminated, and where does the client IP come from?
- How will you see what the cache is doing? Hit ratio, reasons for misses, objects stored, evictions.
Answers 1 to 5 do not depend on the tool. Put them in a short document that the application team also signs, because the application drives most cache behaviour through response headers.
The few HTTP rules that matter
Every tool here implements HTTP caching as defined in RFC 9111, with its own defaults and its own ways to override. You do not need the whole RFC. You need these points.
Cache-Controlfrom the origin decides most things.max-agesets freshness for all caches,s-maxageonly for shared caches like the ones in this post,privatemeans “browser only”,no-storemeans “never store”.no-cacheis commonly misunderstood: it means “store, but revalidate before every use”.- Validators make refreshes cheap. If the origin sends
ETagorLast-Modified, the cache can ask “has this changed?” with a conditional request and get a small304 Not Modifiedback instead of the full body. stale-while-revalidateandstale-if-error(RFC 5861) let the origin say how long a stale response may be served while a refresh runs, or while the origin is failing. Support differs: NGINX reads them; ATS needs a plugin; Vinyl has its own grace model.Varytells the cache which request headers change the response.Vary: Accept-Encodingis normal.Vary: User-AgentorVary: Cookiecan explode the number of copies or make caching useless. NGINX does not cache a response withVary: *.Set-Cookiein a response is a warning sign. NGINX does not cache such a response by default. Vinyl’s built-in VCL also treats it as uncacheable. If your framework sets a session cookie on every page, your hit ratio will be near zero until you fix the application.- Targeted headers. RFC 9213 defines headers like
CDN-Cache-Controlso that the origin can give the shared cache a different TTL from the browser. ATS 10.2 supports this. In other tools you can usually do the same thing with a custom header and a few lines of config.
A useful habit: make every cache add a response header that says hit, miss or stale (for example X-Cache-Status in NGINX). The standard form is the Cache-Status header from RFC 9211, which Souin supports natively.
How each one works
Vinyl Cache and Varnish Cache: policy as code
Vinyl keeps cached objects in memory by default (the malloc storage), or in a memory mapped file (the file storage), which is not persistent across restarts. There is an older persistent storage, now named deprecated_persistent and marked experimental, so plan on an empty cache after every restart. The storage docs also warn that memory fragmentation can make real usage two to four times the configured size, so size the VM accordingly.
The heart of Vinyl is VCL. Each request goes through a set of subroutines (vcl_recv, vcl_hash, vcl_backend_response, vcl_deliver and others). There is a built-in VCL with safe defaults that runs after yours unless you return early. The VCL is compiled to C and loaded into the running process, so you can load a new policy without a restart.
Key caching ideas in Vinyl:
- TTL, grace and keep. TTL is how long an object is fresh. Grace is how long after that it may still be served while a background fetch refreshes it, which is Vinyl’s version of stale-while-revalidate. Keep is how long after that the object is kept only to allow a conditional revalidation.
- Purge and ban. A purge removes one object (and its variants) immediately. A ban adds an expression, such as “all objects whose
x-sectionheader iselections”, that is checked lazily against objects. A background “ban lurker” cleans up bans that only useobj.*fields, which is why good bans test response headers, not request URLs. - Request coalescing. Concurrent misses for the same object wait for one backend fetch. Responses that turn out to be uncacheable become “hit-for-miss” or “hit-for-pass” objects so that waiting does not serialise every request.
- ESI. Edge Side Includes let you cache a page for 10 minutes and the “latest news” fragment inside it for 10 seconds.
- Directors and sharding. The bundled
directorsmodule includes a shard director for consistent hashing across backends or across a layer of caches. - Observability.
vinyllog(formerlyvarnishlog) shows every step of every request from shared memory, without disk I/O.vinylstatgives counters. An OpenTelemetry instrumentation project, VCOT, was announced in November 2025.
A small VCL for a news site, written for the 4.1 syntax that Vinyl still uses:
vcl 4.1;
backend app {
.host = "10.0.1.21";
.port = "8080";
.probe = {
.url = "/healthz";
.interval = 5s;
.timeout = 1s;
.window = 5;
.threshold = 3;
}
}
acl purgers {
"127.0.0.1";
"10.0.1.0"/24;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (client.ip !~ purgers) {
return (synth(405, "Not allowed"));
}
return (purge);
}
if (req.method == "BAN") {
if (client.ip !~ purgers) {
return (synth(405, "Not allowed"));
}
ban("obj.http.x-section == " + req.http.x-ban-section);
return (synth(200, "Ban added"));
}
# logged-in readers and admin pages always go to the app
if (req.url ~ "^/(admin|account|login)" || req.http.Cookie ~ "session_id=") {
return (pass);
}
# anonymous readers: drop analytics cookies so they do not split or block the cache
unset req.http.Cookie;
}
sub vcl_backend_response {
if (beresp.http.Set-Cookie) {
set beresp.uncacheable = true;
set beresp.ttl = 120s;
return (deliver);
}
set beresp.grace = 10m;
set beresp.keep = 1h;
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
}
Two cautions on this example. First, if a TLS proxy sits in front, client.ip is the proxy’s address unless you use the PROXY protocol; give purge requests their own internal listener instead of relying on the IP check alone. Second, removing all cookies for anonymous users is a policy decision. Write down which cookies the application really needs, and test.
TLS is the practical difference between the two projects. Vinyl does not terminate TLS itself; the project’s own tutorial pairs it with HAProxy, and many teams use NGINX for the same job. Varnish Software’s Varnish Cache terminates TLS in-process. If you already run a TLS proxy (as Suryanagari does), this difference matters less.
Apache Traffic Server: a cache built for disks and hierarchies
ATS comes from the CDN and large proxy world. It stores objects on raw devices or large files, organised as spans and volumes, with an in-memory directory pointing to where each object lives, plus a RAM cache for hot objects. By default, the RAM cache size is derived from disk cache size (about 10 MB of RAM per GB of disk, per the docs). The cache survives a restart, which is a real advantage over Vinyl when you hold terabytes, although rebuilding the directory has historically taken minutes on large caches, which is what the new shared memory option in 10.2 addresses.
Key caching ideas in ATS:
- Configuration by files.
records.yamlfor global settings (it replacedrecords.configin 10.0),remap.configto map public URLs to origins,storage.yamlfor disks,ip_allow.yamlfor which clients may use which methods,cache.configfor per URL rules like pinning, andparent.configorstrategies.yamlfor cache hierarchies. - Conservative defaults. By default ATS caches only responses with an explicit lifetime (
required_headersis 2) and ignoresCache-Control: max-agefrom clients, so a client cannot force a refresh. - Read while writer. On by default, so that concurrent misses for the same object read from the one fetch in progress instead of opening many origin connections.
- Purge. The
PURGEmethod removes an object, and the defaultip_allow.yamlallows it only from localhost. 10.2 adds a full logical clear withtraffic_ctl cache clear. For pattern based invalidation there is the Regex Revalidate plugin. - Plugins. Cache key manipulation, cache promotion policies (do not write one-hit wonders to disk), ESI, slice (for very large objects in ranges), background fetch, prefetch, Lua, header rewrite. The Stale Response plugin, which adds
stale-while-revalidateandstale-if-error, is experimental and must be built with experimental plugins enabled. Remember that both critical CVEs of October 2026 were in experimental plugins. - Hierarchies and sharding. Parent selection with consistent hashing lets a set of edge caches send misses to a parent tier, each URL always to the same parent, which is how ATS scales to CDN size.
- Observability. Squid style log codes, many metrics,
stats_over_httpwith Prometheus output, and thexdebugplugin to ask a cache why it did what it did.
A minimal ATS 10 setup for one site, with a 200 GB cache file and the stale plugin on the remap rule:
# records.yaml (excerpt)
records:
http:
server_ports: 8080 8443:ssl
cache:
http: 1
required_headers: 2
cache:
enable_read_while_writer: 1
# storage.yaml
cache:
spans:
- name: disk1
path: /var/cache/trafficserver
size: 214748364800
# remap.config
map https://news.example.in/ http://10.0.1.21:8080/ @plugin=stale_response.so @pparam=--stale-while-revalidate-default=30 @pparam=--stale-if-error-default=600
# ip_allow.yaml (excerpt): allow PURGE from the CMS subnet
ip_allow:
- apply: in
ip_addrs: 10.0.1.0/24
action: allow
methods: ALL
Check every key against the documentation for your exact version before you use this. ATS changes defaults in security releases more often than the other two, and the 10.1 and 10.2 upgrade notes both ask you to read the security hardening section before upgrading.
NGINX proxy cache: the one you may already have
NGINX stores each cached response as a file on disk, named by the MD5 of the cache key, with the keys and metadata in a shared memory zone. The operating system page cache keeps hot files in RAM. A cache manager process removes old entries according to max_size and inactive, and a loader process reads existing entries on start, so the cache survives restarts.
Key caching ideas in NGINX:
- Key. The default
proxy_cache_keyis$scheme$proxy_host$request_uri. Note that$proxy_hostis the upstream name, not the public host, which matters if one location serves several sites. - Freshness. From
X-Accel-Expires,Cache-ControlorExpiresin the response, or fromproxy_cache_validwhen the origin says nothing. - Coalescing.
proxy_cache_lock onlets only one request fill a new cache entry; others wait up toproxy_cache_lock_timeout(5 seconds by default), after which they go to the origin and their responses are not cached. - Stale serving.
proxy_cache_use_stalelists conditions (error,timeout,updating,http_500and so on) under which a stale response is served. Withproxy_cache_background_update on, an expired entry is served stale while a background subrequest refreshes it. - Revalidation.
proxy_cache_revalidate onusesIf-Modified-SinceandIf-None-Matchto refresh expired entries cheaply. - Safety defaults. Responses with
Set-Cookieare not cached, andVary: *is not cached. Both can be overridden withproxy_ignore_headers, which is exactly how the junior engineer at Suryanagari leaked a logged-in page. - Invalidation.
proxy_cache_purgeis commercial. With open source NGINX your options are short TTLs, versioned URLs,proxy_cache_bypassto force a refresh of one key from a trusted client, deleting files by hand, or a third party module. - Observability.
$upstream_cache_status(MISS, HIT, EXPIRED, STALE, UPDATING, REVALIDATED, BYPASS) in a header and in the access log is your main tool. Detailed cache zone metrics are part of NGINX Plus.
A “microcache” for anonymous HTML, with request locking and stale serving:
proxy_cache_path /var/cache/nginx/html levels=1:2 keys_zone=html:50m
max_size=5g inactive=30m use_temp_path=off;
map $cookie_session_id $skip_cache {
default 1;
"" 0;
}
server {
listen 443 ssl;
server_name news.example.in;
# ssl_certificate lines as today
add_header X-Cache-Status $upstream_cache_status always;
location / {
proxy_pass http://app_servers;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache html;
proxy_cache_key $scheme$host$request_uri;
proxy_cache_valid 200 301 60s;
proxy_cache_valid 404 10s;
proxy_cache_lock on;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
}
location ~ ^/(admin|account|login) {
proxy_pass http://app_servers;
proxy_set_header Host $host;
}
}
Note that proxy_cache_valid applies only when the origin sends no caching headers of its own. If the CMS sends Cache-Control: private or no-cache, you must fix the application or knowingly override it.
The others in brief
- Squid is mainly a forward proxy, with an old reverse cache (“accelerator”) mode. Still the right tool for a caching forward proxy in an office or build farm, less so for a new cache in front of a web app.
- Souin and the Caddy cache handler. A Go cache that runs standalone or inside Caddy, Traefik, Tyk and Go frameworks, close to the RFCs, with
Cache-Status, coalescing and tag invalidation. Natural if your edge is Caddy; smaller team, so test it with your own traffic. - HAProxy cache. A minimalist RAM cache: only
200responses, an explicit lifetime or validator required, fewVaryheaders, limited object size. Good for favicons and CSS, not a page cache.
Side by side
Policy, freshness and coalescing
- Vinyl and Varnish: full control in VCL, with TTL, grace and keep per object and built-in coalescing. The most flexible, and the easiest to get subtly wrong.
- ATS: strict defaults, policy spread over configuration files and plugins, read while writer for coalescing, stale serving through settings and the experimental Stale Response plugin.
- NGINX: directives and
mapblocks, enough for most sites; lock, stale serving, background update and revalidation all in the open source version, and in our drill they worked as documented.
Invalidation
- Vinyl: purge, ban with a ban lurker, and tag based invalidation through additional modules. The richest model of the three.
- ATS:
PURGEper URL, Regex Revalidate for patterns, and since 10.2 a full logical clear. - NGINX open source: no built-in purge. Design for short TTLs and versioned URLs, or pay for NGINX Plus.
ESI, TLS and storage
- Vinyl: ESI built in; no TLS (put HAProxy or NGINX in front); RAM or memory mapped file storage, empty after a restart.
- Varnish Cache (Varnish Software): same model, plus in-process TLS.
- ATS: ESI plugin; native TLS with per SNI policies; raw disk or file storage at large scale that survives restarts.
- NGINX: no ESI (SSI only, which had two use-after-free CVEs in 2026); native TLS; files on a normal file system that survive restarts.
Clustering and observability
- Vinyl: no built-in cluster; use the shard director or a consistent hashing load balancer in front.
vinyllogandvinylstatare excellent for “why was this a miss?”. - ATS: designed for hierarchies with parent selection and consistent hashing; detailed logs, Prometheus output and
xdebug. - NGINX:
hash $request_uri consistentin an upstream block spreads URLs across cache servers with no shared state; open source observability is mostly$upstream_cache_statusin logs.
Security record and governance
- Vinyl: three advisories in 2026, each fixed on the day of disclosure, old branches included. BSD 2-clause, independent association.
- Varnish Cache (Varnish Software): shares the Vinyl fixes so far. BSD 2-clause, one company, trademark policy.
- ATS: 49 CVEs across two 2026 advisories, many in parsing and request smuggling, the two critical ones in experimental plugins. Budget for quarterly upgrades and keep experimental plugins off. Apache 2.0, ASF governance.
- NGINX: 26 CVE identifiers in 2026, mostly medium. BSD 2-clause, governed by F5, with purge and cache metrics only in the commercial product.
All four licences are permissive; the real governance question is who decides the roadmap and what lives only in a paid edition. And for all of them, request smuggling and cache poisoning are more serious in a cache than anywhere else, because a bad response is stored and served to thousands. Patch the cache before the application servers behind it.
Performance
All three serve cache hits at a cost far below what an application spends building a page. Published numbers are almost all vendor runs on specific hardware, such as the Varnish Software figure above. For your decision, measure hit ratio and origin load on your own traffic instead of requests per second on a synthetic test. A cache with a 95% hit ratio on the right URLs beats a faster cache with a 60% hit ratio.
A worked plan for Jodhpur
A deliberately boring plan for Devika’s team.
- Agree a caching policy document with the CMS team. List page types (home, section, article, results widget, search, account, admin, API endpoints for the app) with TTL, stale allowance, whether personal, and how they are invalidated.
- Fix the application first. Make sure anonymous pages do not set a session cookie, that personal pages send
Cache-Control: private, no-store, and that the CMS sendsCache-Controlwiths-maxagefor public pages. Remove anyVary: CookieorVary: User-Agentthat is not truly needed. - Start with a microcache in the existing NGINX. Cache anonymous HTML for 60 seconds and the results widget for 5 seconds, with
proxy_cache_lock,proxy_cache_use_stale ... updatingand background update, using a config like the one above. Bypass the cache whenever the session cookie is present. - Add
X-Cache-Statusand log$upstream_cache_status. Build a small dashboard of hit, miss, stale and bypass counts per page type from the access logs. - Load test before the next counting day. Replay a results day pattern with k6 against a staging copy and confirm that origin requests stay flat while client requests rise.
- Test the leak case on purpose. Log in as a test user, load pages, then load them anonymously from another machine, after every CMS release. The incident that scared the team becomes a regression test.
- Handle editor corrections. With a 60 second TTL, corrections show within a minute without any purge. For urgent legal corrections, keep a runbook step: a bypass request from the CMS server that refreshes the entry.
- Decide on purges after three months of data. If editors need instant purges of sections or tags, or if the team wants ESI to cache article bodies for longer while keeping “latest news” fresh, add Vinyl Cache 9.1 LTS behind NGINX: NGINX keeps TLS and serves static files, Vinyl does HTML caching, purge and ban, and the CMS calls Vinyl’s internal purge listener on publish.
- If you add Vinyl, choose the project on purpose. NGINX already terminates TLS, so in-process TLS adds nothing. Prefer Vinyl Cache for independent governance unless you want a Varnish Software support contract, and check what a package called
varnishreally contains. - Leave ATS for later scale. If the e-paper archive and images ever move off the CDN onto their own infrastructure with several terabytes of hot objects, ATS becomes interesting. Today it would be more software than the problem needs.
- Subscribe to advisories for NGINX (and Vinyl, if added), and agree a patch window. The cache is in front of everything.
When not to adopt each
- Vinyl or Varnish: not if nobody will own the VCL, and never chosen by accident through a package name.
- ATS: not for one website with a few gigabytes of hot content; its strengths show at scale, its costs from day one.
- NGINX open source: not if you need instant invalidation of many URLs, unless you accept NGINX Plus or a third party module.
- Any cache: not before the application stops sending
Set-Cookieon public pages.
A short history
- Squid grew out of the Harvest project in the 1990s and was long the default caching proxy.
- Traffic Server started at Inktomi, came to Yahoo with the Inktomi acquisition, and was open sourced through the ASF in 2009.
- Varnish Cache started in 2006 (the project counts 22 February 2006 as its birthday), sponsored by the Norwegian newspaper Verdens Gang and written by Poul-Henning Kamp. Varnish Software later grew out of Linpro, the firm that handled the project’s logistics.
- NGINX added its proxy cache early and became the default “web server plus cache” for many teams; F5 acquired NGINX Inc. in 2019.
- 2025 and 2026: the Varnish open source project became Vinyl Cache, Varnish Software relaunched the Varnish Cache name as its own distribution, and Vinyl 9.1 became the new LTS.
Decision guide
You run one website or API and already have NGINX in front
Start with an NGINX microcache: short TTLs, lock, stale serving, background update, and a clear bypass rule for logged-in users. Measure for a few weeks before adding anything.
Editors need instant corrections and pages are built from many fragments
Use Vinyl Cache behind your TLS proxy, with purge and ban wired to the CMS publish hook, and ESI for fragments. Budget time for someone to own the VCL.
You want the Varnish model, built-in TLS and a vendor to call
Look at Varnish Cache from Varnish Software, and decide whether you also want its commercial support. Read its trademark policy if you plan to redistribute.
You run a CDN like tier with terabytes of objects or several cache layers
Use Apache Traffic Server with parent selection and consistent hashing, keep experimental plugins off, and plan for quarterly security upgrades.
Your edge is Caddy
Add the Caddy cache handler (Souin) and test it against your real traffic, including Vary and invalidation.
You only want to take favicons and CSS off the origin and already run HAProxy
The HAProxy cache is enough. Do not stretch it into a page cache.
Checklist
- Written cache policy per page type: key, TTL, stale allowance, personal or public, invalidation method.
- No
Set-Cookieon public responses; personal responses markedprivateorno-store. - Cache key reviewed for query strings and headers that should or should not split the cache.
Varylimited to headers that really change the response.- Request coalescing on (lock, read while writer, or built-in).
- Stale while revalidate and stale if error configured explicitly, not left to defaults.
- A hit, miss or stale header on every response, and the same value in logs.
- A tested plan for cold start after a cache restart.
- A tested invalidation path, owned by a named person.
- A leak test for logged-in pages after each application release.
- Purge and admin endpoints restricted to internal addresses.
- Advisory subscription and patch window for the cache software.
- For Vinyl or Varnish: a deliberate choice of project and an upgrade path off old branches (8.0 is unsupported, 6.0 ends in April 2027).
Common mistakes
- Caching personal pages by ignoring
Set-Cookie. Overriding the default withproxy_ignore_headers Set-Cookieor forcing a TTL on everything is the classic way to show one user’s data to another. - Letting tracking parameters split the cache. Every
utm_orfbclidvariant of the home page becomes a separate miss. Normalise or strip them in the key. Vary: User-Agent. Thousands of user agent strings mean thousands of copies. Classify into a small device header if you must.- Long TTLs without an invalidation plan. A 24 hour TTL on HTML with no purge path means a wrong headline for 24 hours.
- Trusting response header extensions alone for outages. In our drill, a cached response carrying
stale-if-error=300was not served when the origin returned HTTP 500 in a location withoutproxy_cache_use_stale; the client got the 500. Withproxy_cache_use_stale ... http_500, the stale copy was served. Configure stale serving explicitly and test it. - Exposing PURGE to the internet. Anyone who can purge can empty your cache and send all traffic to the origin.
- Forgetting the cold start. After a restart or a full ban, every request is a miss. Coalescing and a warm-up script for the top URLs help.
- Treating the cache as a WAF. Caching reduces load; it does not filter attacks. Keep those concerns separate.
The drill we ran
We ran a small, loopback-only drill on 31 October 2026 with NGINX 1.30.6, built from the official source tarball in a scratch directory. We built it without the rewrite and gzip modules, because the box had no PCRE or zlib headers, and installed nothing system-wide. The origin was a tiny Python HTTP server on 127.0.0.1 that returned Cache-Control: public, max-age=5, stale-while-revalidate=30, stale-if-error=300, could sleep for one second on some paths, could add a Set-Cookie header, and could be switched to return HTTP 500 for everything.
- Hit and miss. The first request was a MISS and the second a HIT, as expected.
- Stale while revalidate. With a one second slow origin, the first request took about 1.04 seconds (MISS) and the next took well under a millisecond (HIT). After the five second TTL passed, the next request was served STALE in under a millisecond while NGINX refreshed in the background, and the request after that was a HIT with the new version.
- Coalescing. Twenty parallel requests to a cold URL with
proxy_cache_lock onproduced exactly one origin fetch. The same twenty requests to a location without the lock produced fifteen origin fetches. - Cookies. A response with
Set-Cookiewas not cached: two requests, two MISSes, two origin fetches. - Origin errors. With
proxy_cache_use_stale ... http_500, an expired entry was served STALE (HTTP 200, old body) while the origin returned 500. In a location withoutproxy_cache_use_stale, the expired entry was not used and the client received the 500 with status EXPIRED, even though the cached response carriedstale-if-error=300. - All processes were stopped after the drill.
We did not run Vinyl Cache, Varnish Cache or Apache Traffic Server, and we did not test purge (commercial in NGINX), Vary or TLS. Statements about those tools in this post come from their documentation, release notes and advisories, not from our hands. If you repeat the drill, add Vinyl Cache with the VCL above and compare its vinyllog output for the same scenarios.
Unlearn, re-learn
- Unlearn “a cache is a speed setting”. Re-learn that it is a policy about keys, freshness, staleness and invalidation, and write that policy down.
- Unlearn “caching is risky for dynamic sites”. Re-learn that the risk is personal data and stale corrections, and both can be handled with clear rules and tests.
- Unlearn “Varnish is Varnish”. Re-learn that in 2026 there is Vinyl Cache, the continued community project, and Varnish Cache, a company governed distribution, and choose on purpose.
- Unlearn “the cache protects the origin, so it is low risk”. Re-learn that request smuggling and cache poisoning make the cache one of the most sensitive pieces you run, and patch it first.
- Practise a leak test and a cold start test every release, and apply the smallest cache that answers your policy.
Sources
Vinyl Cache and Varnish Cache
- Vinyl Cache homepage and news (releases, advisories, LTS): https://vinyl-cache.org/
- “20 years old and it is time to get serious(er)” (rename announcement, 15 September 2025): https://vinyl-cache.org/organization/20-years.html
- On Vinyl Cache and Varnish Cache (8 April 2026): https://vinyl-cache.org/organization/on_vinyl_cache_and_varnish_cache.html
- Vinyl Cache 9.1 is the new LTS, 6.0 end of life: https://vinyl-cache.org/news/2026-11-29_new_lts.html
- Upgrading to Vinyl Cache 9.0: https://vinyl-cache.org/docs/9.0/whats-new/upgrading-9.0.html
- Changes in Vinyl Cache 9.1.0: https://vinyl-cache.org/docs/9.1/whats-new/changes-9.1.html
- Storage backends: https://vinyl-cache.org/docs/9.1/users-guide/storage-backends.html
- Vinyl Cache source and LICENSE: https://code.vinyl-cache.org/vinyl-cache/vinyl-cache
- Varnish Cache by Varnish Software: https://www.varnish.org/index.html
- Varnish Cache releases on GitHub: https://github.com/varnish/varnish/releases
Apache Traffic Server
- Downloads: https://trafficserver.apache.org/downloads
- Release archive with dates: https://archive.apache.org/dist/trafficserver/
- What’s new in ATS 10.2: https://docs.trafficserver.apache.org/release-notes/whats-new.en.html
- July 2026 security advisory: https://trafficserver.apache.org/security-2026-07.html
- October 2026 security advisory: https://trafficserver.apache.org/security-2026-10.html
- records.yaml reference: https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html
- storage.yaml reference: https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.yaml.en.html
- ip_allow.yaml reference: https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ip_allow.yaml.en.html
- Stale Response plugin: https://docs.trafficserver.apache.org/en/latest/admin-guide/plugins/stale_response.en.html
NGINX
- nginx.org news: https://nginx.org/
- Changes in mainline: https://nginx.org/en/CHANGES
- Changes in 1.30: https://nginx.org/en/CHANGES-1.30
- Security advisories: https://nginx.org/en/security_advisories.html
- ngx_http_proxy_module (cache directives): https://nginx.org/en/docs/http/ngx_http_proxy_module.html
Around them
- HAProxy versions: https://www.haproxy.org/
- HAProxy cache section of the configuration manual: https://docs.haproxy.org/3.4/configuration.html
- Squid releases and advisories: https://github.com/squid-cache/squid
- Souin: https://github.com/darkweak/souin
- Caddy cache handler: https://github.com/caddyserver/cache-handler
- RFC 9111 HTTP Caching: https://www.rfc-editor.org/rfc/rfc9111
- RFC 5861 stale-while-revalidate and stale-if-error: https://www.rfc-editor.org/rfc/rfc5861
- RFC 9211 Cache-Status: https://www.rfc-editor.org/rfc/rfc9211
- RFC 9213 Targeted HTTP Cache Control: https://www.rfc-editor.org/rfc/rfc9213
Related posts on Revisit.Tech
Releted Posts
AI gateways in 2026: revisit LiteLLM, Bifrost and Agent Router before you put another model API key in application code
Most teams did not plan their first AI gateway. It arrived as a side effect. One service called a hosted model with a key from an environment variable.
Read moreInfrastructure access in 2026: revisit Teleport, Boundary and Warpgate before you hand out another SSH key
Every engineering team has an access story it does not like to tell. A new developer joins, and someone appends her public key to authorized_keys on fourteen servers by hand.
Read moreInfrastructure monitoring in 2026: revisit Zabbix, Checkmk and Netdata before you add another host to your monitoring
In most Indian companies, monitoring starts with a person, not a tool. Somebody in the IT team logs in to the file server every morning, checks the disk, looks at the UPS panel, pings the branch router, and goes back to work.
Read more