OPcache Internals — How Bytecode Caching Works
debt(d7/e1/b3/t5)
Closest to 'only careful code review or runtime testing' (d7) — misconfiguration is silent; you only notice via opcache_get_status() inspection or performance monitoring. No standard linter flags missing OPcache config.
Closest to 'one-line patch or single-call swap' (e1) — quick_fix is literally adding two ini directives (extension=opcache, opcache.enable=1) plus tuning a couple of values. No code changes required.
Closest to 'localised tax' (b3) — OPcache configuration lives in php.ini and affects deploy workflows (must restart PHP-FPM, manage memory limits, max_accelerated_files). It imposes ongoing operational awareness but doesn't shape application code.
Closest to 'notable trap (documented gotcha)' (t5) — misconception confuses bytecode caching with output caching, and the silent failure modes (memory full → stops caching, validate_timestamps=0 → stale code after deploy) are classic gotchas devs learn after being burned once.
Also Known As
TL;DR
Explanation
Without OPcache, every PHP request parses source files into an abstract syntax tree, compiles the AST to opcodes, and then executes those opcodes. OPcache intercepts the compile step and stores the resulting opcode array in a shared memory segment accessible to all PHP-FPM worker processes. On subsequent requests, OPcache finds the cached opcodes by filename hash, validates they are still current (via file mtime or inode checks), and passes them directly to the Zend VM — skipping parsing and compilation entirely. PHP 7.4 added preloading: specific files can be compiled at FPM startup and permanently kept in the shared segment, eliminating even the cache lookup for those files.
Common Misconception
Why It Matters
Common Mistakes
- Leaving opcache.validate_timestamps=1 in production — this checks file modification times on every request, partially defeating the purpose of caching.
- Setting opcache.memory_consumption too low — when the shared memory fills up, OPcache silently stops caching new files; check opcache_get_status()['memory_usage'] regularly.
- Forgetting to restart PHP-FPM after deploys when validate_timestamps=0 — workers continue serving old bytecode until restarted.
- Not setting opcache.max_accelerated_files high enough — Composer projects with vendor dependencies often have 10,000+ PHP files; the default of 10,000 may be insufficient.
Code Examples
; ❌ php.ini — OPcache disabled or poorly configured for production
; opcache.enable=1 ; commented out — OPcache inactive
opcache.memory_consumption=64 ; too small for a framework app
opcache.validate_timestamps=1 ; fine in dev, wasteful in production
opcache.revalidate_freq=2 ; checks file mtimes every 2 seconds
opcache.max_accelerated_files=2000 ; too low for Composer projects with 10k+ files
; ✅ php.ini — production OPcache settings
extension=opcache
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256 ; 256MB for large frameworks
opcache.interned_strings_buffer=16 ; Saves memory for repeated strings
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; Disable in production — restart FPM on deploy
opcache.save_comments=1 ; Required for Doctrine/annotations
opcache.preload=/var/www/preload.php ; PHP 7.4+ preloading
opcache.preload_user=www-data