WordPress caching is one of those topics where everyone recommends "just install WP Rocket" without explaining what caching actually is. That's fine until something breaks — and then you don't know which cache layer broke it or how to fix it.
Here's how WordPress caching actually works, from the server up.
The Three Layers of WordPress Caching
WordPress caching happens at three distinct levels. Each solves a different problem. Most performance guides mix them up.
Layer 1: Object Cache (Database Query Cache)
Problem it solves: WordPress runs dozens of database queries per page load. Many of those queries return the same results every time — site options, menu items, widget configurations. Running the same MySQL query 50 times per second is wasteful.
How it works: A key-value store (Redis or Memcached) sits between WordPress and MySQL. The first time a query runs, its result is stored in memory. Every subsequent identical query returns from memory instead of hitting MySQL.
Impact: On a WooCommerce store with a logged-in user, object cache reduces database queries by 40–70%. For static pages already served from a full-page cache, object cache has minimal impact (those pages never hit PHP).
Redis vs Memcached:
- Redis is the better choice. Persistent storage (survives server restart), supports more data structures, and the WordPress Redis Object Cache plugin is well-maintained.
- Memcached is older and simpler. Works fine but Redis is the current standard.
Which hosts include Redis:
| Host | Redis included? |
|---|---|
| Kinsta | Yes, on every plan |
| WP Engine | Object Cache Pro plugin on some plans |
| Cloudways | Add-on ($0 on some plans, $12/mo on others) |
| Hetzner (unmanaged) | Install it yourself (free) |
| Shared hosting | Almost never |
Layer 2: Full-Page Cache (HTML Cache)
Problem it solves: Even with object cache, WordPress still runs PHP to generate HTML for every request. PHP execution takes 50–300ms. For logged-out visitors viewing static pages, this is entirely avoidable.
How it works: The first time a logged-out visitor views a page, PHP generates the HTML normally. That HTML is saved as a static file. Every subsequent visitor gets the pre-built HTML file directly — PHP never runs.
This is why TTFB numbers for cached pages are dramatically lower than uncached:
- Uncached WordPress page: PHP runs, MySQL queries run → 300–500ms TTFB
- Cached WordPress page: static HTML file served → 50–200ms TTFB
Three implementations:
1. Nginx FastCGI Cache (server-level): Stores cached HTML in a directory on the server's disk. Served by Nginx before PHP ever loads. This is what Kinsta uses and it's the fastest implementation.
# Nginx FastCGI cache config
fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=WORDPRESS:10m max_size=1g inactive=60m use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
2. Varnish Cache (server-level): A reverse proxy that sits in front of your web server. All requests pass through Varnish, which serves cached responses from memory. Cloudways uses Varnish. Faster than disk-based caching but uses more RAM.
3. Plugin-level cache (WP Rocket, W3 Total Cache): PHP generates HTML, saves it to disk, serves it on subsequent requests. Slower than server-level (PHP still loads to check if cache is valid), but configurable without server access.
Rule: Server-level cache (FastCGI or Varnish) is always faster than plugin-level cache. If your host supports it, use server-level and let the plugin handle other optimizations (CSS/JS minification, lazy loading).
Layer 3: Browser Cache + CDN
Browser cache: HTTP headers tell the visitor's browser to store images, CSS, and JS files locally. Repeat visitors load those assets from their own device.
CDN (Content Delivery Network): Copies your static assets (images, CSS, JS) to servers around the world. A visitor in Tokyo loads images from a Tokyo CDN node, not your origin server in Frankfurt.
The two are complementary:
- CDN reduces round-trip distance for the initial load
- Browser cache eliminates the request entirely on repeat visits
Kinsta's Cloudflare CDN: Included free, Cloudflare Enterprise tier. HTTP/3 + Brotli compression + smart routing. Visitors within 50ms of a Cloudflare edge node are served from that node.
For unmanaged VPS: Cloudflare's free tier (not Enterprise) is the right starting point. Add the Cloudflare WordPress plugin for automatic cache purging when you publish content.
What WP Rocket Actually Does
WP Rocket is the most recommended WordPress caching plugin. But understanding what it does and doesn't do changes how you configure it:
WP Rocket does:
- Plugin-level full-page caching (slower than server-level, but present if server-level isn't available)
- CSS and JS minification and combination
- Lazy loading images and videos
- Preloading — crawls your site to warm the cache automatically
- Database optimization (transient cleanup, revision removal)
- Defer or delay JavaScript execution
WP Rocket does NOT:
- Replace server-level caching (it's a complement, not a substitute)
- Provide Redis object cache (use Redis Object Cache plugin separately)
- Operate at the CDN layer (it integrates with Cloudflare but doesn't replace it)
Configuration checklist for WP Rocket with a properly configured server:
File Optimization:
✓ Minify CSS
✓ Combine CSS (test — some themes break)
✓ Minify JavaScript
✓ Defer JS (test — can break checkout)
Media:
✓ Lazy load images
✓ Add missing image dimensions
Preload:
✓ Enable preload
✓ Preload links (on hover)
Database (run monthly):
✓ Post revisions (keep 5)
✓ Auto-drafts
✓ Trashed posts
✓ Expired transients
✓ Optimize tables
The Cache Invalidation Problem
The hardest part of caching isn't enabling it — it's knowing when to clear it.
Rules:
- When you publish or update a post: clear that page's cache
- When you update a plugin: clear full cache
- When you run a WooCommerce sale: clear all product pages
- When you update the theme: clear full cache + CSS cache
Most caching systems handle this automatically. Where they fail is edge cases:
- A plugin updates a post's meta without telling the cache
- A WooCommerce price change doesn't trigger a cache invalidation
- A logged-in user's session gets served a cached "logged out" version
WooCommerce-specific cache rules: These pages must NEVER be cached:
/cart//checkout//my-account/- Any page with
?add-to-cart=in the URL
All WordPress caching solutions handle this — but verify your configuration is excluding these pages.
Practical Performance Targets
With full caching stack implemented (server-level page cache + Redis object cache + CDN):
| Page type | Target TTFB | Achievable with |
|---|---|---|
| Blog post, logged out | < 100ms | Kinsta, Cloudways |
| WooCommerce product | < 200ms | Kinsta, Cloudways |
| WooCommerce cart | < 400ms | Kinsta (Redis required) |
| WooCommerce checkout | < 500ms | Kinsta, tuned Cloudways |
If your checkout is over 1s TTFB after implementing object cache and server-level page cache, the bottleneck is likely PHP workers or MySQL query performance — not caching configuration. That's a hosting upgrade conversation, not a plugin configuration conversation.