OPcache Is the Free 2x Speedup You Haven't Configured
PHP's execution model is honest to a fault: every request, parse the source, compile to opcodes, execute, throw the compilation away. OPcache keeps the compiled opcodes in shared memory instead - and while it's usually enabled, the difference between enabled and configured is where a chunk of your response time lives.
The production block
opcache.enable = 1
opcache.memory_consumption = 256 ; MB for opcodes - default 128 fills up on framework apps
opcache.max_accelerated_files = 20000 ; count your files; vendor/ is bigger than you think
opcache.interned_strings_buffer = 16 ; shared storage for repeated strings
; The big decision:
opcache.validate_timestamps = 0 ; NEVER stat files for changes in production
validate_timestamps=0 is the setting with teeth. By default, OPcache stats files (on a timer) to detect edits - sensible for development, pure waste on servers where code only changes at deploy time. Disabling it removes filesystem checks from the hot path entirely. The contract: your deploy must now clear the cache - opcache_reset() via a web/CLI hook, or simply reloading php-fpm. Deploys that "don't take effect" on a tuned server are always this contract being forgotten - it's a rite of passage.
Verify, don't vibe
$s = opcache_get_status(false);
printf("hit rate: %.2f%%\n", $s['opcache_statistics']['opcache_hit_rate']);
printf("memory: %.0fMB used / wasted: %.0fMB\n",
$s['memory_usage']['used_memory'] / 1048576,
$s['memory_usage']['wasted_memory'] / 1048576);
printf("cached files: %d\n", $s['opcache_statistics']['num_cached_scripts']);
Hit rate should sit above 99% in production. High wasted memory means files were invalidated without a reset (deploys piling up dead entries) - fix the deploy hook. If cached files bumps against max_accelerated_files, raise it; the cache silently stops caching new files when full, which produces wonderfully confusing "some pages are slow" tickets.
The two advanced dials, with honest expectations
Preloading (opcache.preload, 7.4+): load your framework's core classes into shared memory once at server start - they skip even the cache-lookup on every request. Worth single-digit percents on framework-heavy apps; requires a restart to update and some care with what you preload. Nice-to-have, not step one.
JIT (8.0+): opcache.jit=tracing, opcache.jit_buffer_size=64M. Real talk - JIT shines on CPU-bound loops (math, encoding, image work) and does very little for typical I/O-bound web requests, where the time goes to the database and rendering. Turn it on (it's free and safe), measure, and don't expect miracles for a CRUD app. The HTTP-level caching and query work will dwarf it.
One dev-machine footnote: XAMPP and friends often ship OPcache disabled or timestamp-validating aggressively - correct for development, and why "it's fast in prod, slow locally" is sometimes just this. Local realism: enable it with validate_timestamps=1, revalidate_freq=0 - cached and instantly fresh.
Total effort: five ini lines, one deploy hook, one status check. It's the highest ratio of speedup-to-work in PHP operations, which is exactly why it's embarrassing how often it's left on defaults.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.