Diagnosing Low OPcache Hit Rate in PHP 7.4+ Web Applications
Identify why OPcache hit rate falls below 80 % and apply targeted fixes based on memory, file‑timestamp, or configuration issues.
23 Dec 2025, 13:45 UTC

Recognizable condition
During peak traffic you notice increased response times and occasional CPU spikes. Checking OPcache status shows a hit rate consistently under 80 % (e.g., 62 %). This indicates that a significant portion of PHP requests are falling back to script compilation instead of serving cached bytecode.
Cause / diagnostic table
| Possible cause | Typical symptom in OPcache stats |
|---|---|
| OPcache memory exhaustion | Used memory close to opcache.memory_consumption; frequent cache misses |
| File‑timestamp changes (e.g., deployment touch) | High opcache_statistics['misses'] with relatively stable used memory |
opcache.max_accelerated_files too low | num_cached_scripts approaching the limit; increased hash collisions |
opcache.save_comments=0 with annotation‑heavy frameworks | Fallback to uncached parsing despite sufficient memory and file count |
Ordered checks
- Collect OPcache status
Run the following CLI command as a user that can execute PHP (e.g., the web‑app user or
www-data). No special privileges are needed beyond being able to invoke the PHP binary.php -r 'print_r(opcache_get_status(true));'Look for the keys:
opcache_statistics['hit_rate'](percentage)memory_consumption['used_memory']andmemory_consumption['free_memory']opcache_statistics['num_cached_scripts']opcache_statistics['misses']
- Compare used memory to limit
If
used_memoryis > 90 % ofopcache.memory_consumption, memory exhaustion is likely. - Check file count versus limit
If
num_cached_scriptsis within 5 % ofopcache.max_accelerated_files, the limit may be too low. - Detect unnecessary timestamp changes
Examine
opcache_statistics['misses']andopcache_statistics['hash_chain_length']. A high miss rate with stable memory suggests frequent invalidations. Correlate with deployment logs orfind /path/to/app -type f -newer /tmp/deploy_markerto see if files are being touched. - Verify annotation handling
If your framework uses docblock annotations (e.g., Doctrine, Symfony) and
opcache.save_commentsis set to0, OPcache will discard comments, forcing the parser to rebuild annotation metadata on each request.
Fixes tied to findings
When memory is exhausted
- Increase
opcache.memory_consumptioninphp.inior the relevant pool configuration (e.g.,/etc/php/7.4/fpm/pool.d/www.conf). Example: raise from 128 M to 256 M. - After editing, restart PHP‑FPM:
sudo systemctl restart php7.4-fpm(requires root or sudo). - Re‑run the status check and verify that
used_memorystays well below the new limit and that hit rate rises.
When file count exceeds max_accelerated_files
- Raise
opcache.max_accelerated_filesto a value comfortably above the observednum_cached_scripts(e.g., if you see 4500 scripts, set to 6000 or 10000). - Restart PHP‑FPM as above.
- Confirm that
num_cached_scriptsno longer approaches the limit and that hit rate improves.
When timestamp changes cause invalidations
- Ensure deployment scripts do not
touchor re‑copy unchanged files. Use atomic swaps or rsync with--checksumto preserve timestamps. - If you cannot change the deployment process, consider lowering
opcache.revalidate_freqto a higher value (e.g., 60) so that OPcache checks less frequently, though this delays picking up genuine changes. - Restart PHP‑FPM after adjusting
opcache.revalidate_freq.
When save_comments=0 breaks annotation‑heavy code
- Set
opcache.save_comments=1in the PHP configuration. - Restart PHP‑FPM.
- Verify that hit rate improves; note that this increases memory usage slightly because comments are retained in the cached bytecode.
Escalation criteria
If after applying the appropriate fix the hit rate remains below 80 %:
- Re‑check the OPcache status to ensure the setting was actually loaded (look at
opcache_get_status()['directives']). - Monitor overall PHP process memory (
ps -o rss,command -C php-fpm7.4) to ensure the server is not swapping. - Consider enabling
opcache.blacklist_filenameto exclude large, rarely‑used libraries that churn the cache. - If the problem persists, gather a longer‑term trend (e.g., via
opcache_get_status()logged every minute) and look for patterns correlated with specific requests or cron jobs. - At this point, a deeper code review or profiling (e.g., with Xdebug or Blackfire) may be needed to identify dynamic code generation that defeats OPcache.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.