Skip to main content
Case Studies

Nginx vs. HAProxy vs. Cloudflare: Which Cache Layer Wins?

We pit nginx, HAProxy, and Cloudflare CDN against each other on cache control, invalidation, and load balancing to see which one actually wins in production.

The Contrarian Take: Your Load Balancer Is Your Cache

Everyone talks about Redis and Memcached as the cache, but the truth is your load balancer and CDN are the first line of defense. Most teams obsess over cache-aside patterns while ignoring the HTTP cache headers that determine whether your edge even stores the response. In our experience, the biggest wins come from the layers closest to the user. So we're going to compare three ways to run that edge: nginx, HAProxy, and Cloudflare. And we're going to make a call: for most teams, nginx wins—but only if you configure it right.

Option 1: nginx—The Workhorse

nginx is the default choice for many of us. It works primarily at Layer 7, handling HTTP headers, URL routing, and TLS termination (system-design-primer). It can act as a reverse proxy with caching via the proxy_cache_path directive, which stores responses in a file-based cache keyed by the MD5 of the cache key (Nginx (proxy module)). It also offers load balancing with round-robin (the default), least_conn, ip_hash, and more (Nginx (HTTP load balancing)). For a typical web app, nginx sits in front of your app servers and can cache API responses, static assets, or even HTML pages.

But here's the catch: nginx's caching is only as good as your Cache-Control headers. If you don't set max-age or s-maxage, nginx won't know how long to keep a response fresh. And if you don't handle invalidation, you'll serve stale data. That's where the HTTP spec comes in.

Option 2: HAProxy—The Pure Load Balancer

HAProxy is a different beast. It's a dedicated load balancer that supports TCP (Layer 4) and HTTP (Layer 7) modes (system-design-primer). It has no built-in caching; it just routes traffic. Its strength is in its load-balancing algorithms: roundrobin, leastconn, source, and more (HAProxy (configuration manual)). For example, leastconn is recommended for long sessions like SQL, but not for short HTTP requests (HAProxy (configuration manual)). So, HAProxy is not a cache—it's a router. You wouldn't use it to cache responses.

But wait—could you use HAProxy as a cache? Technically no, but you could put a cache like Varnish behind it. That's more moving parts. For this comparison, we're looking at what you can do with the tool alone.

Option 3: Cloudflare—The CDN Edge

Cloudflare is a CDN that caches content at the edge, closer to users. Its network spans 348 cities across 8 regions (Cloudflare (network)). By default, it caches certain status codes: 200/206/301 for 120 minutes, 302/303 for 20 minutes, 404/410 for 3 minutes, when no cache-control or expires headers are present (Cloudflare (default cache behavior)). It also supports purge by single-file, which instantly removes a cached resource from all data centers (Cloudflare (purge by single-file)). That's a killer feature for invalidation.

Cloudflare is great for static content and even dynamic content if you set the right headers. But it's a third-party service, and you have less control over the underlying infrastructure. Plus, if you need to cache dynamic responses with complex Vary logic, it can be tricky.

Comparing on 4 Criteria

Let's break it down. We'll look at caching capability, invalidation, load balancing, and ease of configuration.

CriterionnginxHAProxyCloudflare
Caching capabilityBuilt-in, file-based, respects Cache-ControlNoneEdge cache, respects Cache-Control, defaults for status codes
InvalidationManual purge or rely on TTLN/AInstant purge by single-file
Load balancingLayer 7, round-robin, least_conn, ip_hash, etc.Layer 4 & 7, roundrobin, leastconn, source, etc.DNS-based (with Route 53 if you use AWS) or via Cloudflare's own load balancing
Ease of configurationModerate—many directivesSimple for LB, but no cachingEasy via dashboard, but less control

Who Wins? It Depends on Your Traffic

If you're serving mostly static assets and need global reach, Cloudflare is a no-brainer. Its edge caching can absorb 70% or more of origin server requests (AWS (caching overview)). But if you need to cache dynamic API responses with fine-grained control, nginx is the better bet. It lets you set proxy_cache_path and use the full range of Cache-Control directives. HAProxy is for when you need a pure load balancer with no caching—maybe you already have a separate cache layer.

Our recommendation: start with nginx. It's the workhorse that can do both caching and load balancing. Use it to set proper Cache-Control headers and implement cache-aside for dynamic data. For invalidation, you can use the purge feature (though you'll need to implement it) or rely on short TTLs. If you need to scale globally, put Cloudflare in front of nginx. That's a layered approach: browser cache, CDN, then nginx.

Quick Tip: Don't Forget Vary

When you cache responses that vary by header, use the Vary header to define the cache key. Otherwise, you'll serve the wrong variant to different clients (MDN (Vary)).

The Most Important Thing to Remember

Your cache is only as good as your invalidation strategy. Choose a tool that lets you purge precisely, and set your Cache-Control headers deliberately (MDN (Cache-Control)).

Sources

  • system-design-primer - https://github.com/donnemartin/system-design-primer
  • Nginx (HTTP load balancing) - https://nginx.org/en/docs/http/load_balancing.html
  • Nginx (proxy module) - https://nginx.org/en/docs/http/ngx_http_proxy_module.html
  • HAProxy (configuration manual) - https://docs.haproxy.org/3.4/configuration.html
  • Cloudflare (default cache behavior) - https://developers.cloudflare.com/cache/concepts/default-cache-behavior/
  • Cloudflare (purge by single-file) - https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-single-file/

Share this article:

Comments (0)

No comments yet. Be the first to comment!