Setting Up Caching in WordPress the Right Way
In short
Set up caching in this order: first a server or plugin page cache (never both), then browser cache headers, and for shops or many logged-in users an object cache with Redis on top. You can verify it with curl -I and by measuring TTFB before and after. Cart, checkout and logged-in areas must never be cached statically, or users see each other's data.
Caching is the single most effective lever for a fast WordPress site, and the most misunderstood. “Just install a cache plugin” falls short, because caching happens on several layers. Once you know the layers, you make the right decision instead of stacking three overlapping plugins that get in each other’s way. This article is built as a setup guide: first the layers, then the setup in order, then how to check that it actually works.
What Caching Actually Does
WordPress rebuilds every page on every request: PHP runs, the database is queried, HTML is generated, the result goes to the browser. This assembly costs time, visible as a high Time to First Byte (TTFB), the moment until the server sends the first byte. Caching stores the result so the next request serves the finished version, without all that work. This mainly lowers the TTFB and with it the LCP noticeably, and relieves the server during traffic spikes.
The Cache Layers, from Outside In
Each layer solves a different problem. Which ones you need depends on what is slow on your site.
1. Browser Cache (static files at the visitor)
Images, CSS and JavaScript are stored in the visitor’s browser. You control this with response headers, mainly Cache-Control (and the older Expires). max-age sets how many seconds a file counts as fresh. The pattern that has become standard: files whose URL changes whenever the content changes get a very long lifetime, while the HTML itself stays short or must be revalidated before reuse (no-cache). WordPress appends a version parameter like ?ver=… to enqueued scripts and stylesheets by default, which makes them good candidates for long lifetimes.
It makes sense on practically every site. It costs nothing, but only helps returning visitors and people moving between pages, not the first visit. The generator creates the rules for Apache or Nginx:
Caching Generator
Set up browser caching and compression
Pick your server, set the lifetimes, copy the rules. Everything runs in your browser.
Note
- WordPress caching plugins often write these rules themselves already – don't duplicate them.
- Apache: paste the block into
.htaccess– above the# BEGIN WordPressblock.
Your input never leaves the browser. The rules are generated locally via JavaScript.
Also available as a standalone page with all guides: caching generator.
2. Page Cache (the finished HTML)
The biggest impact for ordinary visitors. The complete page is cached as static HTML. There are two routes:
- Server caching (e.g. via the web server or a reverse proxy like Varnish or the Nginx FastCGI cache). The fastest option, because PHP never even starts and the server delivers the stored file directly. If your hosting offers it: first choice.
- Plugin caching (a WordPress plugin generates static files). A solid alternative when no server cache is available. Not both in parallel, that causes conflicts and display glitches.
Technically, a plugin page cache hooks in through a mechanism WordPress provides itself: the file wp-content/advanced-cache.php. More on that below under WP_CACHE.
3. Object Cache (database results)
Stores recurring database queries in memory, usually with Redis or Memcached. WordPress has a built-in object cache, but it only lives for the duration of a single request. Only a persistent object cache survives the page load. It is plugged in through the file wp-content/object-cache.php, a so-called drop-in: if the file is there, WordPress loads it at startup as an external object cache.
A side effect that rarely gets mentioned: without a persistent object cache, transients end up in the wp_options table. With a Memcached or Redis plugin, WordPress keeps them in fast memory instead (that is how the WordPress developer documentation on transients describes it). That takes further load off the database.
When is it worth it? Where the page cache deliberately steps aside: logged-in areas, WooCommerce, membership portals, pages with many dynamic queries. On a small blog with static content it does little. More on the levers in a shop in speeding up WooCommerce.
4. OPcache (compiled PHP code)
OPcache belongs to PHP, not to WordPress. It keeps compiled PHP code in memory so PHP doesn’t have to read and compile the files on every request. According to the PHP manual, opcache.enable is on by default, opcache.memory_consumption defaults to 128 MB, and opcache.validate_timestamps is active (check interval opcache.revalidate_freq, 2 seconds by default). On most current hosting it already runs. Check the phpinfo() output or ask your host. A lot of plugins and a large file count can exhaust opcache.max_accelerated_files (default 10000), and files then drop out of the cache. A current PHP version often helps more than any fine-tuning.
5. CDN and Edge Cache
A CDN stores copies of your files on servers close to the visitor. For images, CSS and JavaScript that is the norm and usually uncritical. HTML is where it gets tricky: according to its own documentation, Cloudflare does not cache HTML or JSON by default. If you change that, you have to make sure logged-in users and the shop cart are excluded. A CDN pays off mainly with an international audience or large media files. For a regional business site with a good server cache it is rarely the first step.
Setting It Up Step by Step
The order matters, because each step builds on the previous one and you always know which change caused which effect.
- Measure a baseline. Note the TTFB before any change (see “Measuring”). Without a before value, no statement about the after value.
- Clarify the host cache. Check the customer area or documentation for a server-side cache and whether it is already active. Many hosts offer something like it, each differently. If yes, enable it there and do not add a second page cache plugin.
- Otherwise: exactly one page cache plugin. Install it, activate the basic settings, page cache on. Nothing more. Minification, lazy loading and the like are separate topics and can be switched on and measured one at a time.
- Check the exclusions. Login, cart, checkout, account (see below).
- Set browser cache headers. Via the cache plugin, the server configuration or
.htaccess. - Add an object cache if you run WooCommerce or have many logged-in users and Redis or Memcached is available.
- Verify and measure. Check the headers, measure TTFB again, then the Core Web Vitals.
Setting Up an Object Cache with Redis
You need a Redis server, either provided by your hosting or run by you. On shared hosting that is often only available in certain plans or not at all. The popular “Redis Object Cache” plugin (named here only as an example) supports Predis, PhpRedis, Relay and more, according to its plugin page. You configure the connection with constants in wp-config.php, including credentials and a prefix if needed:
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PREFIX', 'my-site_' );
The prefix matters as soon as several WordPress installs use the same Redis server: without it they share keys. Then you enable the drop-in either in the plugin settings or via WP-CLI:
wp redis enable
This creates the object-cache.php file in wp-content. After switching it on, check that the site loads normally and that the Site Health test (see below) detects the object cache.
Checking That the Cache Works
“Enabled” doesn’t mean “effective”. There are three ways to check.
Look at the Response Headers with curl
Request the page twice in a row and show only the headers:
curl -sI https://your-domain.com/ | grep -iE 'cache|age|via|etag|last-modified|x-'
The first request after a purge is usually a miss, the second a hit. Which headers your setup sets depends on the cache. WordPress core knows these headers for detection, among others: cache-control with max-age, expires, age, x-cache, cf-cache-status, x-litespeed-cache, x-cache-status, x-proxy-cache and x-varnish. For Cloudflare, the documentation says: HIT means the resource came from Cloudflare’s cache, MISS was cacheable but not present, DYNAMIC is not eligible for cache, and BYPASS was considered eligible but the origin response was not cacheable. If your host sets no visible header at all, you need the second route.
Important: test the real homepage, then a page that should not be cached (cart, wp-login.php). No hit should show up there.
The Site Health Test in WordPress
Since WordPress 6.1, Tools, Site Health runs two cache checks, in production environments only. According to the core source, the page cache test requests the homepage three times, looks for cache headers and rates the response time (default threshold 600 milliseconds). The result reads “Page cache is detected and the server response time is good” when both fit, otherwise a recommendation. Note that the test detects headers, not your intent: a cache without matching headers gets overlooked.
The persistent object cache test has three possible results: “A persistent object cache is being used”, “A persistent object cache is not required” or “You should use a persistent object cache”. It is recommended when the site crosses a threshold. The core defaults: 500 autoloaded options or 100,000 bytes of them, or 1,000 each of comments, options, posts, terms or users. On multisite the test suggests it in any case. Hosts can change these values with a filter, so your result may not match your numbers.
Directly in Code
On the command line you check the object cache like this:
wp eval 'var_dump( wp_using_ext_object_cache() );'
true means WordPress is using an external object cache. If wp-content/object-cache.php isn’t there, expect false.
WP_CACHE and advanced-cache.php
Many sites have this line in wp-config.php:
define( 'WP_CACHE', true );
According to the WordPress documentation it does not switch caching on by itself. It only makes WordPress load the file wp-content/advanced-cache.php early during startup, if it exists. A comment in core says, roughly, that it is a static drop-in because you would only want one. That explains typical failure patterns: two page cache plugins fight over the same file, and the constant without the file does nothing.
Excluding the Cache Correctly
The most common caching mistake isn’t a missing cache but a misapplied one. These areas must never be served statically:
- Cart, checkout and account in WooCommerce, otherwise one customer sees another’s cart.
- Logged-in areas: membership pages, client portals, the admin backend.
- Form and preview pages with personalised or session-dependent content.
According to its source code (the WC_Cache_Helper class), WooCommerce sets the DONOTCACHEPAGE constant on these pages, so a cache plugin that respects it leaves them out. If yours doesn’t, you have to exclude cart, checkout and My Account by hand. The WooCommerce docs also list cookies that point to an active cart or session: woocommerce_cart_hash, woocommerce_items_in_cart and wp_woocommerce_session_. A cache that takes these into account treats customers with a filled cart correctly.
After setup, always test two things: login and checkout, once as a logged-in user and once in a private window. Put a test product in the cart and check in a second browser that nothing shows up there.
When Does the Cache Get Cleared?
A page cache has to know when a stored page is outdated. Good solutions clear the affected pages when you publish or update a post, and also clean up after a set lifetime. Exactly how this happens differs by plugin and host, including for menus, widgets, comments or theme changes. So check it once yourself: change a post, save, reload the page in a private window. If it still shows the old version, clear it selectively or shorten the lifetime. Clearing constantly, on the other hand, makes the cache pointless, because every request runs uncached again.
Avoiding Conflicts
- Two page cache plugins compete for the same file,
advanced-cache.php. The result: display glitches that come and go. - Host cache plus plugin cache doubles the work. At best it gains nothing, at worst visitors see old versions because you only cleared one of the two layers. Pick one layer as the page cache. Use the plugin at most for what the host doesn’t provide, and turn off its page cache. Many hosts have their own setting or a bundled plugin for this, so check your host’s documentation.
- Object cache and page cache are no conflict, they complement each other. The
object-cache.phpdrop-in is a different file fromadvanced-cache.php. - CDN plus plugin cache: clear both layers after bigger changes, otherwise the CDN serves an old version.
Measuring: TTFB Before and After
The most honest number for caching is the TTFB. Measure it with curl against the homepage:
curl -o /dev/null -s -w 'connect: %{time_connect}s ttfb: %{time_starttransfer}s\n' https://your-domain.com/
Run the command several times and take a middle value instead of a single shot, which is prone to network fluctuation. The comparison is what counts: the first request after a purge (uncached) against the following ones (cached). If there is no difference between them, the cache isn’t working. For the overall picture from the user’s side, the Core Web Vitals help.
The Typical Mistakes
- Several cache plugins at once, they override each other and cause hard-to-trace display glitches.
- Not excluding the cache where needed, personalised pages never belong in the page cache.
- Panic after every change: modern caches clear themselves on content changes; only clear deliberately when you see stale states.
- Caching as a substitute for cleaning up: a cache hides an overloaded page, it doesn’t heal it. Too many plugins and a bloated database remain a problem.
When Caching Alone Isn’t Enough
Caching speeds up delivery, but it doesn’t replace a clean foundation. If a page loads 40 plugins, serves huge unoptimised images or runs on a bloated database, the cache only masks that for the first impression. The first, uncached request, and Google’s crawler can hit one too, stays slow. Caching therefore belongs together with optimised images, a lean plugin selection and a tidy database. Hosting sets a limit too, which you can read up on in WordPress hosting: what to look for.
Not sure which cache layer actually helps your setup? Drop us a line, we’ll set up caching cleanly, without cart or login getting muddled. To keep it that way, our WordPress maintenance looks after cache, updates and database together.
Frequently Asked Questions
Do I even need a cache plugin?
Not necessarily. If your hosting ships a server-side page cache (for example via Nginx FastCGI or Varnish), it's faster than any plugin because PHP never even starts – a separate cache plugin is then superfluous and can even cause conflicts. Only when no server cache is available does a single, established cache plugin take over the job.
Server cache or plugin cache – which is better?
Server caching is the better choice when your hosting offers it: the finished page is served without PHP and the database even starting up. Plugin caching generates static files inside WordPress and is a solid alternative when there's no server cache. Important: never run both in parallel, as that leads to contradictory states and hard-to-trace display glitches.
What is an object cache and when do I need it?
An object cache (Redis or Memcached) stores the results of database queries in memory so WordPress doesn't re-run the same query on every request. It works mainly where the page cache doesn't reach: in logged-in areas, with WooCommerce and on heavily dynamic pages. For a small blog it's usually unnecessary; for a shop it's almost mandatory.
Do I have to clear the cache manually after every change?
Usually not. Modern caches detect content changes and clear the affected pages themselves. You should only clear manually if you still see a stale state despite a saved change – for example after direct edits to templates or CSS. Clearing constantly makes the cache pointless.
How can I tell that the page cache really works?
Request the page twice with curl -I and compare the response headers. On the second request a cache header such as x-cache, cf-cache-status or an Age value should point to a hit, and the TTFB should drop measurably. Which headers your setup sets depends on the cache, so first look at what differs between the first and second request.
What does WP_CACHE do in wp-config.php?
The constant does not switch caching on by itself. When set to true, WordPress loads the file wp-content/advanced-cache.php early during startup, if it exists. That is exactly the file page cache plugins and some hosts create. Without it, WP_CACHE does very little.
How do I see whether my WordPress uses a persistent object cache?
Open Site Health under Tools. Since version 6.1, WordPress reports there whether a persistent object cache is in use, not required or recommended. With an active object cache there is also an object-cache.php file in wp-content.
Related Services
Founder & Full-Stack Developer
20+ years of web development experience. Specialised in Laravel, WordPress and custom software for mid-sized businesses.