The fastest request is the one your server never has to process. Web caching stores copies of content closer to the visitor, or in a ready-made form on the server, so pages load faster and servers handle more traffic with less hardware. Caching happens at several layers, each with its own rules and pitfalls. This article walks through them from the visitor's browser back to your database, and explains the HTTP headers that control them.
The layers of web caching
| Layer | Where it lives | What it stores | Controlled by |
|---|---|---|---|
| Browser cache | Visitor's device | Images, CSS, JS, sometimes pages | HTTP response headers |
| CDN cache | Edge servers around the world | Static files, optionally whole pages | Headers plus CDN rules |
| Reverse proxy cache | In front of your app (Nginx, Varnish) | Generated HTML and API responses | Proxy config and headers |
| Application page cache | Inside the app or CMS plugin | Rendered pages or fragments | Application settings |
| Object cache | Redis or Memcached | Query results, sessions, computed data | Application code |
| Opcode cache | PHP OPcache | Compiled PHP scripts | php.ini |
Browser caching
When a browser downloads a file, the response headers tell it whether and how long it may reuse that file. The main header is Cache-Control:
Cache-Control: public, max-age=31536000, immutable
max-age: seconds the response is considered fresh (31,536,000 is one year).public: shared caches such as CDNs may store it.privaterestricts storage to the user's own browser, appropriate for personalised pages.no-cache: may be stored, but must be revalidated with the server before each use. Despite the name, it does not mean "do not cache".no-store: do not store at all; use for sensitive data such as account pages.immutable: the file will never change at this URL, so the browser need not revalidate.
Revalidation uses ETag or Last-Modified headers. The browser asks "has this changed?" and, if not, the server replies 304 Not Modified with no body, saving bandwidth.
The safe pattern for static assets is versioned file names (app.4f2c1.js) with a one-year max-age, and short or revalidated caching for HTML. When you deploy, the HTML references new file names, so visitors pick up changes immediately.
In Nginx:
location ~* \.(?:css|js|woff2|png|jpg|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Our HTTP header checker shows exactly which caching headers any URL returns.
CDN caching
A content delivery network (CDN) keeps copies of your content on servers in many locations. A visitor in Mumbai is served from a nearby edge rather than your origin server in Frankfurt, cutting latency, and your origin handles far fewer requests.
CDNs follow your Cache-Control headers by default, and many respect s-maxage, which applies only to shared caches. That lets you cache a page briefly at the CDN while browsers revalidate:
Cache-Control: public, max-age=0, s-maxage=300
Key CDN considerations:
- Cache keys: which parts of the request (path, query string, cookies, headers) make a response unique. Too broad and the hit rate collapses; too narrow and users see each other's content.
- Cookies: most CDNs bypass caching for responses that set cookies or requests carrying session cookies. Logged-in areas usually should not be edge-cached.
- Purging: know how to clear specific URLs or tags after content changes.
Server-side caching
Reverse proxy and page caching
Generating a CMS page can involve dozens of database queries. A page cache stores the finished HTML and serves it directly to anonymous visitors. Options include Nginx FastCGI cache, Varnish, LiteSpeed Cache, or a CMS plugin. A minimal Nginx FastCGI cache:
# in the http block
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PAGES:100m inactive=60m;
# in the http block: skip the cache for logged-in WordPress users
map $http_cookie $skip_cache {
default 0;
~wordpress_logged_in 1;
}
# in the PHP location block
fastcgi_cache PAGES;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache-Status $upstream_cache_status;
The X-Cache-Status header (HIT, MISS, BYPASS) makes it easy to confirm the cache is working. Always bypass for logged-in users, carts, checkout and POST requests.
Object caching
Redis or Memcached keep frequently used data in memory: results of expensive queries, session data, configuration. WordPress, Magento, Laravel and most frameworks support them. Object caching helps even for logged-in users, where page caching is not possible. Keep Redis bound to localhost or a private network and protected with authentication.
Opcode caching
PHP's OPcache stores compiled scripts in memory so PHP does not re-read and parse source files on every request. It is bundled with PHP and should be enabled on every production server; check with php -i | grep opcache.enable.
Cache invalidation
The hard part of caching is making sure visitors do not see stale content. Practical strategies:
- Versioned assets remove the problem for CSS, JS and images entirely.
- Short TTLs for HTML (minutes) limit how stale a page can be.
- Purge on publish: CMS plugins and CDN APIs can clear affected URLs when content changes.
- Never cache personal data publicly. Mark account, cart and checkout pages
private, no-store.
Common mistakes
- Long cache times on unversioned files, so updates do not appear.
- Caching pages that contain a logged-in user's name or cart at a shared layer.
- Several caching plugins fighting each other in one CMS.
- Forgetting that caching hides, rather than fixes, a very slow uncached page; cache misses still happen.
Designing a caching setup that is fast and safe is part of the tuning work in our server management service.
Key takeaways
- Web caching operates at browser, CDN, reverse proxy, application, object and opcode layers.
Cache-Controlheaders drive browser and CDN behaviour;no-cachemeans revalidate,no-storemeans never store.- Use versioned static files with long lifetimes and short or revalidated caching for HTML.
- Bypass shared caches for logged-in and personalised pages, and plan how to purge.