Diagnosing PHP OPcache Problems: When Opcode Caching Is Off, Full, or Serving Stale Code
Slow pages, high CPU, or stale code after deploys often trace back to OPcache. A diagnostic guide with a cause table, ordered checks, targeted fixes, and verification steps.
26 Aug 2026, 01:22 UTC

If your PHP application suddenly feels slow, CPU usage climbs under normal traffic, or code changes don't appear after deployment, the Zend OPcache is one of the first places to look. OPcache stores compiled PHP bytecode in shared memory so the engine skips parsing and compiling on every request. When it's disabled, undersized, or misconfigured for deployments, you pay full compile cost per request — or worse, serve yesterday's code.
This guide assumes PHP 7.4 or newer, where OPcache ships bundled with PHP (it only needs enabling, not a separate PECL install). Commands below target PHP-FPM on a Debian/Ubuntu-style layout; adjust paths for your distro.
The recognizable condition
Typical symptoms: response times noticeably higher than expected for mostly-static PHP work, elevated CPU on the web servers, or a phpinfo() page with no OPcache section even though php.ini appears to enable it. A subtler variant: everything is fast, but newly deployed code isn't executing — a sign of timestamp validation being disabled without a cache flush in the deploy pipeline.
Cause and diagnostic table
| Symptom | Likely cause | How to confirm |
|---|---|---|
No OPcache section in phpinfo() | Extension not loaded | php -m | grep -i opcache returns nothing |
opcache.enabled=1 in php.ini but cache inactive | Runtime override disabling it | opcache_get_status()['opcache_enabled'] is false; check .user.ini or ini_set() calls |
| High miss rate, sluggish under load | opcache.memory_consumption too low | memory_usage.used_memory sits near the configured limit |
| Cache keeps filling and resetting | opcache.max_accelerated_files exceeded | opcache_statistics.num_cached_keys pinned at the max |
| Old code runs after deploy | opcache.validate_timestamps=0 with no flush step | Check the ini value; touch a file and see if changes appear |
Ordered checks
- Confirm the extension is loaded. Run
php -m | grep -i opcachefrom the shell. For the web SAPI (which is what actually matters), load aphpinfo()page and look for the Zend OPcache section. CLI and FPM use separate ini files — a green check in one proves nothing about the other. - Confirm it's enabled at runtime. Run a small script through the web SAPI:
If<?php $s = opcache_get_status(false); var_dump($s['opcache_enabled']); var_dump(ini_get('opcache.enabled'));opcache_enabledisfalsewhile the ini value is1, something (a.user.ini, a pool-levelphp_admin_value, or application code) is turning it off. - Check memory pressure. Inspect
$s['memory_usage']:used_memory,free_memory, andwasted_memory. If free memory is near zero, the cache is evicting or failing to store scripts. - Check file capacity. Compare
$s['opcache_statistics']['num_cached_keys']againstopcache.max_accelerated_files. A full key table means some scripts never get cached. - Check timestamp validation. If
opcache.validate_timestamps=0, OPcache never re-reads files; every deploy must flush the cache explicitly.
Fixes tied to findings
Extension missing
Install and enable it, then restart PHP-FPM. On Debian/Ubuntu, as root:
apt-get install php-opcache
# or ensure the ini exists: /etc/php/8.x/fpm/conf.d/10-opcache.ini
systemctl reload php8.2-fpmAdjust the package and service names to your PHP version. A reload is required — ini changes do not apply to running FPM workers.
Runtime-disabled cache
Search for overrides: grep -r opcache.enabled /etc/php/, check the FPM pool config for php_admin_value[opcache.enable], and look for .user.ini files in the document root. Remove the override, set opcache.enabled=1 in the FPM ini, and reload FPM.
Memory or key capacity too low
Raise the limits in the OPcache ini, then reload FPM:
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.interned_strings_buffer=16Size max_accelerated_files above your project's total PHP file count (including vendor). Pick the next prime-ish value above that count; the setting accepts specific values and rounds internally.
Stale code after deploys
Two workable strategies:
- Development: keep
opcache.validate_timestamps=1with a lowopcache.revalidate_freq(e.g., 2 seconds) so edits appear quickly. - Production: keep
validate_timestamps=0for speed, but add a flush to the deploy: either callopcache_reset()from a protected endpoint or reload PHP-FPM (systemctl reload php8.2-fpmis graceful and drops the cache).
A caution on comments
Do not set opcache.save_comments=0 or opcache.load_comments=0 if your stack uses Doctrine annotations or similar docblock-based metadata. Stripping comments breaks those libraries in ways that look unrelated to caching.
Verifying the fix
After reloading FPM, re-run the status script and confirm:
opcache_enabledistrueused_memoryis comfortably belowmemory_consumptionnum_cached_keysrises under traffic and then stabilizes below the maxopcache_statistics.hitsgrows much faster thanmisses
For an end-to-end check, run a simple load test (for example ab -n 500 -c 20 https://your-host/page) before and after the change and compare average response time and server CPU. Do this against a staging environment if possible; load tests on production carry obvious risk.
Limitations and escalation
opcache_get_status() can be disabled in hardened setups (opcache.restrict_api), in which case you'll need shell access and ini inspection instead. Also note the CLI does not use OPcache by default (opcache.enable_cli=0), so command-line checks of behavior can mislead.
Escalate beyond configuration when: memory and key limits are generous yet misses stay high (possible per-request cache resets from something calling opcache_reset()), when multiple FPM pools fragment the cache, or when you're on a pre-7.4 PHP where OPcache behavior differs — at that point, profile with a tool like Blackfire or XHProf to confirm compilation is actually your bottleneck before tuning further.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.