Magento performance depends on the full caching stack
People talk about "the Magento cache" as if it were one system. A production store has several caching layers, and each one stores a different kind of data.
OPcache stores compiled PHP. Redis or Valkey stores application data and sessions. Varnish stores complete public pages. A request should stop at the earliest layer that can answer it. If one layer is missing or unhealthy, more work reaches PHP and the database.
This article explains what each layer stores, how a request moves through the stack, which configuration mistakes show up often, and how to measure the result.

OPcache keeps compiled PHP in memory
OPcache is part of PHP. It stores the compiled bytecode from PHP files so PHP does not have to parse and compile those files on every request.
Magento has a large PHP codebase. Without OPcache, PHP repeats that compile work across requests. With OPcache, PHP can reuse compiled code from memory.
Magento's cache commands do not manage OPcache. That separation makes it easy to miss during a performance review. Every Magento cache type can be enabled while PHP still spends too much time compiling code because OPcache is too small or misconfigured.
Size OPcache for the Magento codebase
OPcache needs enough memory and file slots to hold the compiled codebase. The opcache.memory_consumption setting controls its memory area. The opcache.max_accelerated_files setting controls how many PHP files it can track. Both need to match the installed Magento core, extensions, and custom modules.
In production, set opcache.validate_timestamps to off so PHP does not check every file for changes on every request. Production code should change through a deploy. The deploy process must restart PHP-FPM or reset OPcache after the new code arrives.
Use Redis or Valkey for the default cache
Magento's application cache stores merged configuration, layout data, block HTML, and other generated values. Put this cache in Redis or Valkey so the data is shared across web nodes and read from memory.
File-based cache is local to one web node. A request sent to another node may miss that cache and rebuild the same data. File writes also add disk work during busy traffic.
The backend configuration lives in app/etc/env.php. It identifies the Redis or Valkey host, port, database number, and cache settings. Confirm that every web node points to the same intended backend.
Keep sessions separate from the cache
Magento sessions can also use Redis or Valkey, but they should use a separate database number or instance from the application cache. A full Redis flush against a shared database can remove session keys and log customers out.
Separate databases make the roles easier to clear, inspect, and alert on. They do not create separate memory on the same Redis server, so the server still needs enough maxmemory for both workloads.
Sharing one database can appear to work for a long time. The failure usually appears during a flush, a traffic spike, or memory pressure, when cache churn begins to interfere with active sessions.
Put Varnish in front of Magento
The full-page cache stores complete rendered pages. Magento can use its built-in PHP application or Varnish. Varnish is the usual choice for a store with enough public traffic to benefit from serving pages before PHP starts.
The built-in full-page cache still uses PHP to serve a cached page. Varnish sits in front of PHP-FPM and can return a cached response without starting Magento. That removes application and database work from a cache hit.
Magento selects the full-page cache application through its configuration. A value of 2 selects Varnish. Verify the deployed value for system/full_page_cache/caching_application; a server can have Varnish installed while Magento still sends traffic through its built-in option.
Use cache tags to invalidate Varnish
Magento and Varnish coordinate through cache tags. Magento marks a cached page with the catalog, category, or other entities that page uses. When an entity changes, Magento sends a purge request for the matching tags.
A product save can then remove pages that show that product while leaving unrelated pages cached. The store keeps serving warm content while the changed pages rebuild on their next request.
Bad tag coordination creates two opposite problems. Varnish can serve stale content if the purge never arrives, or it can purge too broadly and make too many requests cold. Test purges after product, category, price, and inventory changes.
Each layer exists to stop a request before it reaches the more expensive layer below.
How the layers work together on a hit
The layers form a path from the edge to the database. Each layer can stop the request before it reaches the more expensive layer below. On a Varnish hit, Varnish returns the response and PHP, Redis, and the database do no work for that request.
That is the main performance gain. Public category and product requests should often finish at Varnish. PHP and the lower layers handle requests that need fresh or customer-specific data.
Logged-in actions, checkout, cart updates, and the first request for a purged page usually travel farther down the stack. Those requests need healthy OPcache, Redis, PHP-FPM, and database performance.
What happens on a miss
A Varnish miss sends the request to PHP-FPM. OPcache supplies compiled PHP, Magento reads application values from Redis or Valkey, and the application queries the database for data that is not cached.
A miss at the top puts work on every layer below it. If Redis is evicting keys or OPcache is recompiling files, each Varnish miss becomes slower.
This is why a low full-page cache hit rate has a large cost. Many requests pay for the complete application path instead of receiving a response at the edge.
Watch Redis memory and eviction
A Redis cache needs enough memory for its configured roles. When it reaches maxmemory, Redis follows its eviction policy and removes keys. An evicted cache key becomes a future miss.
Check Redis INFO output for hits, misses, memory use, and the evicted_keys counter. A steadily rising eviction count points to a capacity or policy problem. Adding application code will not fix a Redis server that cannot hold its assigned data.
Refresh the config cache during deploys
Magento merges module, environment, and store configuration into cached data. A stale configuration cache can make a new setting appear to have no effect after deployment.
Include the correct cache cleanup in the deploy process. For a configuration-only change, a targeted command such as bin/magento cache:clean config can refresh the affected type without removing every cached page.
Know which cache type holds the value you changed. A blanket flush hides that information, empties Varnish and Redis, and makes the next requests rebuild the whole stack.
Use private content for customer-specific data
Personalized data creates a risk for shared full-page cache. Magento handles this with private content. The public page stays cacheable while customer-specific pieces load in the browser through the customer-data mechanism.
The cart summary, customer name, and other private sections can then update for one customer without changing the shared response served to everyone else.
Do not put customer-specific output into a shared page cache. If a block cannot separate public and private data, it may make the entire page uncacheable. That choice sends every request through PHP and the lower layers.
Put a CDN in front of Varnish when needed
A content delivery network can cache static assets close to shoppers and can cache public pages at edge locations. That adds a layer above Varnish and keeps more requests away from the origin.
The CDN and Varnish need a coordinated invalidation plan. If Magento purges Varnish but the CDN keeps an old response, shoppers can still receive stale content. Test cache headers and purge behavior at both layers.
A CDN does not replace Varnish. The CDN handles edge delivery, Varnish handles the origin's public page cache, and PHP, Redis or Valkey, and the database handle requests that need application work.
Valkey is a Redis-compatible option
Valkey is a Redis fork that began after Redis changed its license. For Magento cache and session use, both systems use the Redis protocol and the same general backend settings.
The operational choice includes licensing, hosting support, version compatibility, and the team's ability to monitor and upgrade the service. Test the exact Magento client and server versions before changing production.
The basic architecture stays the same: use an in-memory key-value service for cache and sessions, assign roles deliberately, and monitor memory and eviction.
Give each Redis role its own database
Separate the default cache, page cache, and sessions into their own Redis database numbers or instances when Redis backs those roles. The database number makes targeted cleanup and inspection safer.
A page-cache cleanup should not remove application configuration, and an application-cache cleanup should not remove sessions. Verify the database number in app/etc/env.php for every role.
Separate databases also make reports easier to read. You can compare key counts, hit and miss data, memory use, and eviction by role. If the roles share one Redis instance, remember that they still share the server's total memory limit.
Use the block HTML cache below full-page cache
The block HTML cache stores the rendered output of individual Magento blocks. On a page that Varnish cannot share, Magento can reuse cached fragments instead of rendering every block from scratch.
This helps logged-in views and other pages with private content. It does not make a page safe to share when the block contains customer-specific data. The cache key and invalidation rules still need to separate private values.
The block HTML cache normally uses the application cache backend, so it uses the same Redis or Valkey capacity. Track it when a page misses Varnish and still takes too long to render.
Test OPcache preloading as an advanced change
PHP 7.4 added OPcache preloading. It loads selected files into memory when PHP starts, so those files are ready for later requests.
Preloading can reduce repeated startup work, but Magento's generated code, autoloading, and deploy process need careful testing. Preloaded code stays in the running PHP process until it is reset, so a deploy must restart the relevant workers.
Fix OPcache size and timestamp validation first. Then compare request time, memory use, and error logs with and without preloading. Keep the change only if the measurements improve a Reproducible workload.
Warm public pages after a flush
A flushed cache is empty until requests refill it. After a deploy or broad cleanup, the first request for each page pays the cost of running PHP, reading Redis, and querying the database.
Warm the public route list after the deploy. A crawler can fetch key category, product, and content pages so Varnish and the block HTML cache have useful entries before normal traffic arrives.
Warm only public, cacheable routes and respect the site's request limits. Do not use a crawler to create customer sessions or cache private responses. Without a plan, real shoppers become the warm-up traffic.
Common misconfigurations
These mistakes can leave the stack enabled while removing much of its benefit:
- OPcache stays at a default size and evicts compiled files under the Magento codebase.
- The full-page cache uses the built-in option while Varnish is installed and expected to serve public pages.
- Sessions and application cache share one Redis database, so a full Redis flush can remove active logins.
- Redis evicts keys or custom blocks make important pages non-cacheable, producing a low hit rate.
These failures often produce slow requests instead of clear errors. Measure each layer before changing application code.
Measure the layers separately
Each layer exposes different health data. OPcache status shows memory use, cached scripts, and hit information. Redis INFO reports hits, misses, memory, and evictions. Varnish reports cache hits and misses through varnishstat.
Read the numbers together, but do not collapse them into one score. A high Varnish hit rate with stale purge behavior is a content problem. A high Redis hit rate with rising evictions is a capacity problem. A high OPcache hit rate with slow PHP points to work elsewhere in the request.
Record the baseline, the configuration DIFF, and the relevant output in the log Directory. Use a Reproducible route list and workload when comparing changes. That turns "the store feels slow" into a testable statement such as "the full-page cache hit rate is 40 percent and these routes miss."
Each caching layer reports its own hit rate; a healthy stack shows strong numbers at all three.
Make a full flush the last resort
Prefer targeted cleanup. Removing only the entries affected by a change keeps the rest of Varnish, the block cache, and the application cache warm.
A full flush for a small configuration or content change creates a cold start under live traffic. The store then rebuilds public pages, fragments, and application data while customers wait.
Use a full flush when the change requires it, and plan the cache-warming step afterward. For smaller changes, clean the specific cache type or purge the affected tags.
Set up the stack from the bottom up
Start with OPcache, because PHP uses it for application requests. Configure Redis or Valkey for the application cache and sessions next. Put Varnish in front after the origin responds correctly without it.
Validate each layer before adding the next. A Varnish hit can hide an origin problem, so test a cache miss and confirm PHP-FPM, Redis, and the database complete the request within the expected time.
Once the layers work together, a strong Varnish hit rate keeps lower layers quiet, and healthy lower layers make the remaining misses cheaper.
Review the stack from the outside in
Start with Varnish: confirm public pages hit, purge when catalog data changes, and do not cache private responses. Check the CDN too if one sits in front of Varnish.
Then inspect Redis or Valkey for separate roles, enough memory, and rising evictions. Check OPcache for capacity, file slots, timestamp validation, and deploy resets. Finally, test a full cache miss through PHP-FPM and the database.
Keep the configuration DIFF with the deployment record, the measurements in the log Directory, and the route test Reproducible. A caching review should leave the team with evidence for each layer, not a guess about which cache to flush next.