PHP OPcache and JIT: complete configuration
OPcache caches compiled PHP bytecode and removes the need to recompile it on every request. JIT (Just-In-Time, available since PHP 8.0) compiles bytecode into machine code — a speed-up of up to 3x for CPU-intensive tasks.
Basic OPcache configuration
[opcache]
opcache.enable=1
opcache.enable_cli=0
; Memory for the bytecode cache (MB)
opcache.memory_consumption=256
; Memory for interned strings (MB)
opcache.interned_strings_buffer=16
; Maximum number of files in the cache
opcache.max_accelerated_files=20000
; Check files for changes (0 = never, production)
opcache.validate_timestamps=0
; Recompile on change (dev: 1, prod: 0)
opcache.revalidate_freq=0
; Keep comments (required for Doctrine/annotations)
opcache.save_comments=1JIT configuration (PHP 8.0+)
; JIT buffer
opcache.jit_buffer_size=128M
; JIT modes:
; 0 = disabled
; tracing (1255) — the best one for web applications
; function (1205) — for CLI/scripts
opcache.jit=tracingProfiles for different environments
; --- PRODUCTION ---
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.jit=tracing
opcache.jit_buffer_size=128M
; --- DEVELOPMENT ---
opcache.validate_timestamps=1
opcache.revalidate_freq=0 ; check on every request
opcache.jit=0 ; JIT interferes with xdebugMonitoring the OPcache status
<?php
$status = opcache_get_status();
$config = opcache_get_configuration();
echo 'Hit rate: ' . round($status['opcache_statistics']['opcache_hit_rate'], 2) . '%';
echo 'Used memory: ' . round($status['memory_usage']['used_memory'] / 1024 / 1024, 1) . ' MB';
echo 'Cached scripts: ' . $status['opcache_statistics']['num_cached_scripts'];A hit rate above 95% is a good result. Below 80% — increase opcache.memory_consumption.
Flushing the cache on deployment
# Through the PHP CLI
php -r "opcache_reset();"
# Through nginx + PHP-FPM: reload the workers
php-fpm8.3 -t && systemctl reload php8.3-fpmReal JIT and OPcache Errors
Failed to allocate the JIT buffer
PHP Warning: Zend OPcache: Failed to create JIT buffer: mmap() failed [12]: Cannot allocate memory in Unknown on line 0The value of opcache.jit_buffer_size exceeds what the process can actually be given — on a VPS with little RAM, or inside a container under a hard cgroup limit. Sometimes the cause is not the amount of memory but kernel security policies (SELinux/grsecurity) that forbid executable memory pages for JIT — in that case, check dmesg for denials.
JIT gets disabled when Xdebug is loaded
php -v
PHP 8.2.18 (cli) (built: ...)
with Zend OPcache v8.2.18, Copyright (c), by Zend Technologies
with Xdebug v3.3.1, Copyright (c) 2002-2024, by Derick RethansIf Xdebug shows up in this output, JIT effectively does not work, even if opcache.jit=tracing is set in the config — the two extensions cannot run together. Xdebug should never be present in production, but this is a common reason for "JIT is not speeding up anything at all" when testing a config on a staging server with the debugger still enabled.
When JIT Does Not Fit
- Ordinary CRUD applications on WordPress, and most Laravel projects, are bottlenecked on I/O (database, disk, network) rather than CPU — JIT delivers a few percent there, or nothing at all.
- Shared hosting with a per-process memory cap — the extra 64–256 MB needed for
jit_buffer_sizemay simply not fit inside the limit. - Code running with an active byte-code cache but with Xdebug enabled for debugging — JIT is useless in that configuration, so disable it explicitly on dev environments.
Verifying the Result
| What to check | Command |
|---|---|
| JIT is enabled and active | php -r "var_dump(opcache_get_status()['jit']['enabled']);" |
| The actual JIT buffer size | php -i | grep jit_buffer_size |
| Any warnings at FPM startup | php-fpm8.3 -tt 2>&1 | grep -i jit |
The -tt flag on php-fpm prints an extended configuration test with every warning, including module loading problems — the plain -t only reports fatal syntax errors.
Before turning JIT on in production, test the actual workload on a staging copy: measure response time for key pages before and after using ab or wrk. If the difference falls within measurement noise, leave JIT disabled — extra code in production without a benefit only increases the surface for the JIT compiler's own rare, hard-to-reproduce bugs.
In practice, for a typical online store or blog, the difference between opcache.jit=tracing and JIT fully disabled turns out to be within statistical noise — OPcache alone already delivers the whole gain, without JIT.