...

Detecting and Fixing PHP OPcache Fragmentation for Maximum Performance

OPcache fragmentation noticeably slows down PHP applications because the cache breaks free memory into small pockets, making it harder to accommodate new bytecode blocks. I'll show you how to Fragmentation reliably identify, systematically resolve, and permanently prevent through clean deployments and appropriate OPcache configuration.

Key points

The following key points provide you with a quick roadmap that you can follow step by step, allowing you to Performance stabilize.

  • Key figures Read: used/free/wasted_memory, current_wasted_percentage, opcache_hit_rate.
  • Thresholds Set: wasted_memory < 5, % (good); if 15–30, take action with %.
  • Configuration Increase: memory_consumption, max_accelerated_files, interned_strings_buffer.
  • Reset-Strategy: Scheduled opcache_reset() or service restart with warmup.
  • Deploy-Discipline: stable paths, controlled invalidation, monitoring.

Why OPcache Fragmentation Occurs

OPcache stores compiled bytecode in the Shared Memory—but frequent deployments, changing directories, or frequent plugin changes leave gaps. Such gaps cannot be used as contiguous blocks, causing the cache to operate inefficiently and leading to more frequent recompilation of bytecode. Short revalidation intervals drive up the number of invalidations and exacerbate the pattern of fragmented memory blocks. Limits that are too low for memory or file indexes increase evictions and contribute to an unstable memory layout. I first examine deployment practices and prioritize consistent paths; otherwise, the Fragmentation with each release.

Understanding the Key OPcache Metrics

I regularly evaluate used_memory, free_memory, and wasted_memory because these metrics reflect the actual Cache-quality. The `current_wasted_percentage` metric is particularly important, as this percentage can be easily linked to fixed thresholds. If the `opcache_hit_rate` drops noticeably below 99 %, this trend indicates untapped potential or fragmentation. I also keep an eye on `num_cached_scripts` to determine whether limits on file entries are causing bottlenecks. Without these metrics, you’re left in the dark when faced with fluctuating Performance in the dark.

Thresholds that trigger action

With a value of 5 % wasted_memory, an OPcache usually runs without causing any issues, so I leave the Settings For now. Starting at 15 %, I plan to take countermeasures, especially if free_memory also becomes low. At 30 % of wasted_memory at the latest, the cache is effectively considered to have shrunk, and I trigger a reset or reboot. If free_memory drops to about 10 % and the cache reports that it is full, the fragmentation spiral accelerates. Such thresholds make decisions clear because they Action Instead of relying on gut feelings, force yourself to act.

Accurately Interpreting Symptoms During Live Operation

Consistent increases in latency across many endpoints indicate a broad-spectrum effect brake such as fragmentation. An increasing CPU load with the same traffic volume also points to more frequent recompilations due to a fragmented cache. A persistently low hit rate after warmup further confirms this pattern. If OPcache restarts or evictions occur frequently without major code changes, the cache simply lacks space in contiguous blocks. I map these indicators to the metrics and then implement targeted Measures from.

Reliably Monitor and Read OPcache

A small script using `opcache_get_status()` gives me the necessary Data directly from PHP. For quick checks, I use `phpinfo()`; for trend analysis, I regularly log the values to the monitoring system. I visualize `wasted_memory`, `hit_rate`, and `free_memory` so that gradual deterioration becomes apparent. Time-based comparisons after deployments show whether certain release patterns accelerate fragmentation. Without this historical perspective, it’s difficult to identify the causes categorize.

Configuration: Set Storage and File Limits Correctly

I use `opcache.memory_consumption` to set the size of the Memory Based on the codebase: small WordPress setups often run well with 128–256 MB, medium-sized sites with 256–384 MB, and larger online stores require 384–512 MB or more. I use `opcache.max_accelerated_files` to prevent too few file indexes from lowering the caching rate; 8,000–10,000 for small WordPress sites and 20,000+ for WooCommerce or large frameworks have proven effective. I count the PHP files, including vendor files, and set the limit to 1.3–1.5 times that number. If you want to dive deeper, you can find background information on OPcache configuration in a practical guide. Solid limits stabilize the memory layout and reduce the Fragmentation noticeable.

Using Internalized Strings and Huge Code Pages

I use `opcache.interned_strings_buffer` to minimize duplicates Strings in memory; 16–32 MB help larger projects use the space more efficiently. Those with higher traffic often benefit from slightly larger buffers. Optionally, `opcache.huge_code_pages` can speed up execution, provided the system supports large pages. Less administrative overhead usually means slightly lower latencies and tends to result in less fragmentation. I only enable this option after running tests to ensure that no Surprises arise during operations.

Setting Up Timestamp Validation and Revalidation Correctly

Timestamp checks that are too aggressive often invalidate bytecode and cause the Fragmentation up. In development, I keep `validate_timestamps=1` and `revalidate_freq` low so that changes are visible immediately. In production, I choose moderate intervals of 60–300 seconds or set `validate_timestamps=0` plus an explicit OPcache reset upon release. If you want to investigate the causes more deeply, use analytics to OPcache validation and potential performance spikes. With controlled invalidation, memory remains more contiguous, and the hit rate stable.

Targeted Elimination of Fragmentation: Reset Strategies

If `wasted_memory` rises significantly and the hit rate drops, I start a Reset via opcache_reset() during quieter periods. Immediately afterward, I run a warm-up of important routes to quickly fill the cache and avoid load spikes. Alternatively, I restart PHP-FPM or Apache, which completely refreshes the shared memory segment. After each reset, I monitor the hit rate, `wasted_memory`, and `free_memory` to ensure the cache recovers as planned. Scheduled overnight restarts work well for setups that experience more frequent Fragmentation build.

Prevention: Clean Deploys and Warm-ups

I deploy releases to new directories and use a symlink to point to a fix I change the path to something like /var/www/html/current so that OPcache doesn't hold onto legacy data from previous paths. Immediately after the switch, I perform a controlled reset. A script that requests popular pages, REST routes, and store views specifically warms up the cache. This quickly raises the hit rate to a high level, and users barely notice the maintenance window. With this discipline, the Fragmentation permanent.

Practical Tips for WordPress, WooCommerce, and Frameworks

WordPress blogs with few plugins often benefit from a `memory_consumption` setting of 128–256 MB and a `max_accelerated_files` setting of at least 8,000, along with a `revalidate_freq` of 60–120 seconds. Larger WooCommerce stores run more reliably with 256–512 MB of memory, 20,000+ max_accelerated_files, and an interned_strings_buffer of 16–32 MB. Frameworks such as Laravel or Symfony often require 20,000–40,000 file indexes and 256–512 MB or more, depending on the size of the vendor. If you want to avoid common pitfalls, you’ll find a concise guide to OPcache Configuration Errors in WordPress setups. The following table summarizes useful Standard values together.

Project type memory_consumption max_accelerated_files revalidate_freq interned_strings_buffer
Small WordPress 128–256 MB 8,000–10,000 60–120 s 8–16 MB
WooCommerce/Medium 256–384 MB 20.000+ 60–180 s 16–32 MB
Large Store/Multisite 384–512 MB+ 30.000+ 120–300 s 32–48 MB
Laravel/Symfony 256–512 MB+ 20.000–40.000 60–180 s 16–32 MB

Advanced: Preloading and JIT Without Side Effects

Starting with PHP 7.4, I can use `opcache.preload` to load frequently used classes and functions at startup. This reduces cold-start latency and stabilizes the hit rate. I note that preloading is closely tied to the PHP process lifecycle: If preloaded files change, I schedule a targeted restart of PHP-FPM/Apache because such changes do not take effect properly with just `opcache_reset()` alone. In PHP 8.x, it’s also worth taking a look at JIT: The `opcache.jit_buffer_size` parameter reserves separate memory for JIT compilation. JIT does not directly affect OPcache metrics, but it can improve CPU load and response times. When JIT is enabled, I test warm-up and memory headroom particularly carefully to ensure that no additional pressure is placed on the shared memory segment.

Understanding Allocator Details: Free vs. Wasted

OPcache manages shared memory in chunks. When scripts are deleted or replaced, gaps are created that often do not exactly match the size of new bytecode blocks. These gaps count as wasted_memory. free_memory In contrast, contiguous, usable memory is more practical. A high percentage of „wasted“ memory while there appears to be “plenty of free space” is a classic scenario that masks the actual capacity. I monitor how quickly `wasted_memory` grows after deployments: If the ratio skyrockets after just a few minutes, I interpret this as a sign of unstable paths, revalidation intervals that are too short, or a lot of changing code (e.g., frequently regenerated template files). Huge code pages reduce administrative overhead and can thus slightly mitigate the tendency toward fragmentation, but they are no substitute for a clean deployment strategy.

A Methodical Approach to Sizing: How to Determine the Correct Size

Instead of just increasing it „by feel,“ I take a systematic approach:

  • I measure the peak values of used_memory after a full warm-up plus daily load.
  • I add up the average `wasted_memory` value during stable periods (after a reset, before deployments).
  • I'm planning for 20–30 % of headroom to account for releases, seasonal demand, and growth.

These components are used to calculate a target value for `opcache.memory_consumption`. For `opcache.max_accelerated_files`, I count all PHP files (including vendor files) and set the limit 30–50 % higher than the actual number of files to account for fluctuations caused by updates. After making the adjustment, I check whether `num_cached_scripts` remains consistently well below the limit and whether the hit rate stays stable above 99 % after warmup.

Warm-up Playbook: Get Up to Operating Temperature Quickly and Effectively

A warm-up prevents cold-start spikes and distributes bytecode more evenly. I use two levels:

  1. Technical Warmup: I trigger key routes (Home, Login, Shopping Cart, Checkout, Search API) in parallel.
  2. Content Warm-up: I load highly trafficked pages and REST endpoints from logs and analytics.

Example of a compact warmup script (Shell):

#!/usr/bin/env bash
set -euo pipefail
BASE="https://example.org"
URLS=(
  "/" "/wp-login.php" "/shop/" "/cart/" "/checkout/"
  "/wp-json/wp/v2/posts?per_page=1" "/wp-json/wc/store/products?per_page=1"
)
for u in "${URLS[@]}"; do
  curl -fsS -m 10 -H "User-Agent: Warmup" "$BASE$u" &
done
wait

For deeper integrations, I can also use a PHP endpoint that calls `opcache_compile_file()` for frequently accessed files. Important: Warm-up scripts should be included in the release pipeline, immediately after the reset and before traffic is opened.

Deployment Methods: Blue/Green, Rolling, Symlinks

Blue/green deployments with a fixed symlink path prevent path fluctuations. For rolling deployments across multiple application servers, I strictly synchronize the steps: First, synchronize the new code; then perform a reset and warm-up on each host; and finally, switch the traffic over. With PHP-FPM, I distinguish between a reload and a restart: A reload reloads configurations but often allows the existing shared memory segment to continue running; a Restart It recreates the segment and reliably eliminates fragmentation. Under Apache, with PHP running as a module, I achieve the same effect with a clean restart. For each environment, I clearly document which command „actually“ clears the OPcache so that nightly maintenance windows can be scheduled as planned.

Special Cases: Multi-Tenant, CLI, and Worker

In multi-tenant setups, I use separate FPM pools and set `opcache.validate_permission=1` so that one tenant does not use another tenant’s code. This enhances security and reduces unexpected cache collisions. For CLI jobs, I check `opcache.enable_cli`: By default, it is disabled, which does not affect fragmentation in the web path. However, if I’m running long-running CLI workers, enabling CLI OPcache can be useful—in which case the same rules apply for reset and warmup. For dynamically generated or frequently changing PHP files (e.g., build artifacts, templating output), I blacklist them using `opcache.blacklist_filename` to prevent churn and, consequently, fragmentation.

Fine-Tune File and Path Validation

I use `opcache.revalidate_path` to specify whether OPcache should re-resolve paths when `include_path` or symlinks are changed. In stable production environments, I usually leave the value set to 0. If I switch between releases via symlinks, I check whether the application depends on this feature—if necessary, I enable revalidate_path on a case-by-case basis. `file_update_protection` prevents overly frequent recompiles immediately after file changes (a short protection window in seconds). In build pipelines that replace files atomically, I keep the value moderate so that newly deployed code is quickly added to the cache. The `opcache.file_cache` (second-level cache on disk) is optionally useful for warming up faster after restarts; it is not a substitute for shared memory, but it reduces cold-start overhead and thus the frequency of frantic compiles.

Common Mistakes and Anti-Patterns

  • „More memory solves everything“: A cache that’s too large without proper discipline will eventually lead to fragmentation. Clarify your deployment and reset strategy first.
  • „Reload is enough“: In many environments, the shared memory segment remains intact. For a true reset, I plan to perform a restart or use opcache_reset() followed by a warmup.
  • „A 98 % hit rate is okay, right?“ Under load, 1–2 additional %s result in noticeable latency spikes. The goal remains > 99 % after warmup.
  • „We invalidate constantly—it’s safer“: Frequent invalidations accelerate fragmentation. Better: controlled invalidation at release points.

Troubleshooting: A Structured Approach

  1. Record status: opcache_get_status(), used/free/wasted_memory, num_cached_scripts, opcache_hit_rate.
  2. Check thresholds: Is `wasted_memory` >= 15 % or `free_memory` <= 10 %? If so, plan countermeasures.
  3. Verify limits: `max_accelerated_files` vs. actual number of files, `interned_strings_buffer` vs. string usage.
  4. Test Reset+Warmup: Perform during a quiet period; compare metrics before and after.
  5. Customizing Deployment Patterns: Fixed Paths, Symlink Switching, Invalidation Only on Release.
  6. Refine monitoring: Track trends over days and weeks; correlate peaks with deployments.

A minimalist status endpoint for monitoring is very helpful to me in practice:

<?php
header('Content-Type: application/json');
echo json_encode(opcache_get_status(false));

A quick summary for everyday life

I keep wasted_memory below 5 %, the hit rate above 99 %, and free_memory well below the 10 % threshold, because such thresholds clearly Signals deliver. If the values rise into critical ranges, I immediately schedule a reset with a warm-up or increase the cache and file indexes in a carefully calculated manner. Deploying to stable paths and performing controlled invalidation prevent legacy data from clogging the cache. Continuous monitoring reveals patterns that mere snapshots do not show. With this approach, the Performance consistently, and the OPcache acts as a reliable performance booster rather than a source of risk.

Current articles