Skip to main content

PHP OPcache and JIT: fine-tuning for maximum speed

PHP · 19.04.2026 · 4 min read
Illustration for “PHP OPcache and JIT: fine-tuning for maximum speed”

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=1

JIT 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=tracing
Important: JIT does not speed up I/O-bound applications (WordPress, a typical CRUD project). A noticeable gain comes from mathematical computations, image generation and machine learning.

Profiles 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 xdebug

Monitoring 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-fpm
Tool: Install opcache-gui — a clear web interface for monitoring OPcache in real time.

Real 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 0

The 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 Rethans

If 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_size may 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 checkCommand
JIT is enabled and activephp -r "var_dump(opcache_get_status()['jit']['enabled']);"
The actual JIT buffer sizephp -i | grep jit_buffer_size
Any warnings at FPM startupphp-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.

Was this article helpful?
← Back to Knowledge Base Ask Support