How to speed up WordPress: what each cache layer really does
This guide walks through the caching stack of a typical WordPress site from top to bottom: what each layer does, why some pages bypass it, in which order to enable everything, and how to verify the result with real numbers. It is written for site owners and administrators who can edit wp-config.php and run a few shell commands over SSH.
How the layers stack up
A request to a WordPress page passes through several stations: the visitor's browser, the web server with its full-page cache, the PHP interpreter with OPcache, the object cache, and finally MySQL. Each layer sits in front of the next one, and a hit at a higher layer means everything below it does no work at all. A full-page cache hit skips PHP and the database completely. An object cache hit still runs PHP but skips repeated database queries. That is why the layers are not interchangeable: each one helps a different kind of traffic.
Full-page cache: the biggest single win
A full-page cache stores the final HTML of a page and serves it to the next visitor without starting WordPress at all. For anonymous traffic this is the largest improvement you can make: a cached response takes a few milliseconds instead of several hundred, because no PHP runs and no database query executes.
Logged-in pages and shopping carts bypass this cache by design. Their HTML is personalized: the admin bar, the user name, the cart contents. Serving a cached copy would show one visitor another visitor's data. Caching plugins and server rules therefore skip the cache whenever they see cookies such as wordpress_logged_in_* or woocommerce_cart_hash. The practical consequence: a shop where most buyers are logged in gains far less from a page cache and depends heavily on the layers below it.
Check whether a page came from the cache by reading the response headers:
curl -sI https://example.com/ | grep -i -E "x-cache|age|cache-control"
Object cache: what Redis actually saves
By default WordPress rebuilds everything from MySQL on every request: autoloaded options, user and post metadata, taxonomy relationships, plugin settings, menu structures. Most of these queries return the same result thousands of times a day. A persistent object cache stores those results in Redis, in memory, so the next request reads them without touching the database.
The effect is largest exactly where the full-page cache is powerless: wp-admin, logged-in users, WooCommerce checkout, REST API calls. An uncached page view often runs 100 or more queries; with a warm Redis cache that number drops to a few dozen.
wp plugin install redis-cache --activate wp redis enable wp redis status
If wp redis status reports a connection and a growing number of keys, it works. A typical site needs only a modest amount of Redis memory: 64 to 256 MB is usually enough.
OPcache: stop compiling PHP on every request
PHP is compiled to bytecode before execution. Without OPcache the interpreter recompiles WordPress core, the theme and every plugin, easily a few thousand files, on each request. OPcache keeps the compiled bytecode in shared memory, so this cost is paid once after the code changes instead of continuously.
Any recent PHP build ships OPcache and usually has it enabled for the web server. Verify it through the web SAPI, not the command line: php -r runs in the CLI, where opcache.enable_cli is off by default, so a CLI check reports false even when PHP-FPM has OPcache working fine. Drop a temporary file into the web root, request it over HTTP, then delete it:
echo ' /var/www/html/opcache-check.php curl -s https://example.com/opcache-check.php rm /var/www/html/opcache-check.php
bool(true) means OPcache is active for real web requests. Sensible php.ini settings for WordPress: opcache.memory_consumption=192 and opcache.max_accelerated_files=20000. If you deploy code by copying files, keep opcache.validate_timestamps=1 so changes are picked up.
Browser caching and image formats
The cheapest request is the one the browser never sends. Static assets: CSS, JavaScript, fonts and images should be cached on the client for a long time. WordPress appends a ?ver= query string to enqueued assets, so a long expiry is safe: when a file changes, its URL changes too. A typical nginx rule:
location ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
Image format matters as much as image caching. WebP is typically 25 to 30 percent smaller than an equivalent JPEG, and AVIF another 15 to 20 percent smaller than WebP at the cost of slower encoding. Convert on upload with a plugin or a cron job, keep the original as a fallback, and do not re-encode files that are already optimized: every lossy pass costs quality.
Order of work, measurement, and the five-plugin myth
Enable the layers in this order and measure after each step, not once at the end:
- Verify OPcache. It is the foundation, carries no risk and needs no plugin.
- Full-page cache. The biggest win for anonymous traffic.
- Redis object cache. It helps logged-in users, wp-admin and WooCommerce, exactly the traffic the page cache cannot serve.
- Browser caching and expires headers.
- WebP or AVIF for images.
The number to watch is TTFB, time to first byte, because caching layers act before the first byte leaves the server:
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s total: %{time_total}s\n" https://example.com/
Run it 5 times and discard the first result, which may hit a cold cache. An uncached WordPress page commonly shows a TTFB of 300 to 800 ms; a full-page cache hit should stay well under 100 ms.
Finally, the myth: installing five caching plugins does not stack five speedups. Two page caches serve stale copies of each other, double minification breaks JavaScript, and a purge in one plugin misses the copy held by the other. Use one tool per layer, and prefer a page cache at the server level rather than inside PHP.
On our WordPress hosting plans this whole stack, server-level page cache, Redis and OPcache, comes preconfigured, so the checklist above reduces to measuring. If you prefer to build it yourself, every one of our web hosting plans supports the same layers.