CDNs, proxies and load balancers

HTTP & the web · Advanced · 12 min

Almost every production site is served through these layers, and their behaviour depends on your HTTP headers.

Requests rarely go straight to your application.

A reverse proxy (nginx, HAProxy, Envoy, Caddy) accepts client connections and forwards them to backend servers. It terminates TLS, compresses responses, serves static files, enforces limits and adds headers. A load balancer is a proxy that spreads traffic across several backends using health checks.

Behind a proxy, your app sees the proxy's IP. The original client data travels in Forwarded or the de facto X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host headers. Only trust them when set by your own proxy; clients can forge them.

A CDN (Cloudflare, Fastly, CloudFront, Akamai) runs reverse proxies in hundreds of locations, called edge servers or PoPs. Users connect to a nearby edge, which shortens TCP and TLS handshakes, and the edge serves cached responses or fetches from your origin.

The CDN cache follows your headers:

  • Cache-Control: public, s-maxage=600 lets shared caches keep a response for 10 minutes while browsers use max-age.
  • private or no-store keeps responses with user data off the edge. Cookies and Authorization usually make CDNs bypass the cache by default.
  • Vary adds headers to the cache key, e.g. Vary: Accept-Encoding.
  • The Age response header shows how long a copy has been cached; providers add hit/miss headers such as cf-cache-status.

Invalidating ("purging") is possible but slow and error-prone. Hashed asset URLs avoid it entirely. CDNs also absorb DDoS traffic and can run code at the edge.

Example

# Is the response served from the CDN cache?
$ curl -sI https://www.example-shop.com/assets/app.3f9a1c.js | grep -iE "cache-control|age|cache-status|x-cache"
cache-control: public, max-age=31536000, immutable
age: 48211
cf-cache-status: HIT

# nginx as a reverse proxy in front of an app on port 3000
#   location / {
#     proxy_pass http://127.0.0.1:3000;
#     proxy_set_header Host $host;
#     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#     proxy_set_header X-Forwarded-Proto $scheme;
#   }

Remember

  • Reverse proxies terminate TLS and forward to backends
  • Trust X-Forwarded-* only from your own proxy
  • CDN edges cache by your Cache-Control (s-maxage for shared caches)
  • Keep personalised responses private or no-store

Watch out for

  • Caching a personalised page publicly at the CDN, so one user's data is served to others.

Check yourself

  1. Which directive lets a CDN cache a page for 10 minutes while browsers revalidate every time?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching