Zend OPcache Explained: Installation, php.ini Settings and Disabling JIT

Test environment RHEL familyAlmaLinux 9.8root accountChecked 2026-10-16

Zend OPcache is a PHP extension that compiles PHP scripts to bytecode and stores it in shared memory, so the compile step is skipped the next time the same file runs. After enabling JIT on a WordPress server running PHP 8.3 caused a 500 error on every page, the server was recovered by leaving OPcache on and disabling only JIT.

Since PHP 5.5, OPcache has shipped with PHP by default.

1️⃣ Zend OPcache concept: bytecode caching

Every time a request arrives, a PHP script goes through three steps: reading the file, compiling it to bytecode, and executing it. When OPcache is running, the first two steps drop out from the second request for the same file onward, and only execution remains.

The shared memory where OPcache stores bytecode is used by all PHP-FPM workers together. If several sites are connected to the same PHP-FPM, the scripts of those sites are also stored in one shared memory.

🧩 Request handling without and with OPcache
Explainer diagram
🧠 OPcache
📨 Request
🧰 PHP-FPM Workers
Worker 1First request
Worker 2Second request
Worker 3wait
Three steps on every requestFirst request · three stepsExecution only→
⚙️ PHP script processing
Read fileSkipped
→
Bytecode compilationSkipped
→
RunPHP execution · database query
Shared memory not used↓ Bytecode stored↑ Bytecode from shared memory used
🧠 Shared memory
Bytecode (compiled PHP code)
Shared by all PHP-FPM workers · one shared memory even with several sites
Without OPcache, a PHP script goes through three steps on every request: reading the file, compiling it to bytecode, and executing it.
Without OPcache, the second request for the same file also goes through all three steps again: reading the file, compiling it to bytecode, and executing it.
With OPcache running, the first request goes through the three steps and then stores the bytecode in shared memory.
From the second request for the same file onward, the first two steps drop out and only execution remains; the shared memory is used by all PHP-FPM workers together.

What OPcache stores is compiled PHP code, so PHP execution and database queries still happen every time a page is opened. Skipping PHP execution altogether by storing finished HTML is the job of a page cache.

2️⃣ Checking the PHP OPcache installation

You can check whether OPcache is installed in the last line of the [php -v] output.

php -v
PHP 8.3.22 (cli) (built: Jun  3 2025 17:17:57) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.3.22, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.22, Copyright (c), by Zend Technologies

If the last line is missing, install it with dnf. The package name is usually php-opcache; if several PHP versions were installed side by side from the Remi repository, a version is attached, as in php83-php-opcache.

dnf -y install php-opcache

The php-opcache package from the Remi repository loads the extension through the [zend_extension=opcache] line in [/etc/php.d/10-opcache.ini]. After installing, restart PHP-FPM so that OPcache also applies to web requests.

3️⃣ OPcache settings: php.ini values

OPcache settings can go in two places: the [opcache] section of [/etc/php.ini] and [/etc/php.d/10-opcache.ini].

php --ini
Configuration File (php.ini) Path: /etc
Loaded Configuration File:         /etc/php.ini
Scan for additional .ini files in: /etc/php.d
Additional .ini files parsed:      /etc/php.d/10-opcache.ini,

Files in the php.d folder are read after php.ini, so if the same entry is set in both places, the php.d value applies.

The table below shows the PHP 8.3 defaults and the values applied on the WordPress server that was checked.

ItemPHP 8.3 defaultApplied valueMeaning
opcache.enable11Turns OPcache on
opcache.enable_cli01Also used by command-line PHP (wp-cli and others)
opcache.memory_consumption1281024Shared memory that stores bytecode (MB)
opcache.interned_strings_buffer816Memory that stores each identical string only once (MB)
opcache.max_accelerated_files1000010000Upper limit on the number of keys that can be cached (the real limit is a prime number at or above this value: 16229 for 10000)
opcache.max_wasted_percentage510If memory runs short and wasted space exceeds this percentage, the cache is cleared and restarted
opcache.validate_timestamps11Checks whether files have changed
opcache.revalidate_freq260How often file changes are checked (seconds)
opcache.jittracingdisableJIT mode (the default is disable from PHP 8.4)
opcache.jit_buffer_size00JIT memory; 0 means JIT does not run (the default is 64M from PHP 8.4)

The values placed in the [opcache] section of [/etc/php.ini] are shown below. The memory size is set to match the server memory, and [opcache.max_accelerated_files] to match the number of WordPress files.

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=1024
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.max_wasted_percentage=10
opcache.validate_timestamps=1
opcache.revalidate_freq=60

If a [zend_extension] line is added to php.ini again, the extension is loaded twice and a [Cannot load Zend OPcache] warning is printed.

With [opcache.revalidate_freq] at 60, old code can keep running for up to 60 seconds after a PHP file is edited.

On a WordPress server where PHP files change often, keep [opcache.validate_timestamps] at 1 so that file changes are checked. The 0 that the Symfony documentation recommends for production servers suits servers where PHP files change only when a new version is deployed.

Even with [opcache.validate_timestamps] set to 0, WordPress itself clears the cache of changed files when an update is made from the admin screen. But if you update with wp-cli or edit files over SSH, the old code keeps running until PHP-FPM is restarted.

4️⃣ Disabling OPcache JIT

PHP 8's JIT speeds up CPU-heavy work, so its effect is small in an application like WordPress that handles web requests. The WordPress benchmark in the PHP JIT RFC shows the same: 326 requests per second with JIT enabled and 315 with JIT disabled.

JIT was enabled on a WordPress server running PHP 8.3.22 with [opcache.jit=tracing] and [opcache.jit_buffer_size=128M]. Three days later, on a day when a rush of requests made the PHP-FPM workers restart several times, a 500 error occurred on every page.

The error log contained the message [Allowed memory size of 1073741824 bytes exhausted]. The [tried to allocate 4295229440 bytes] that follows it records an attempt to allocate 4GB at once under a PHP memory limit of 1GB. After JIT was disabled, the same requests were handled normally.

JIT is disabled by putting the two lines below in [/etc/php.d/10-opcache.ini]. Even if JIT settings remain in [/etc/php.ini], the values in this file apply.

opcache.jit=disable
opcache.jit_buffer_size=0

In PHP 8.3 the default of [opcache.jit_buffer_size] is 0, so JIT does not run unless it is set separately. From PHP 8.4, the default of [opcache.jit] is also disable.

5️⃣ Applying and verifying the settings

Changed settings take effect only after PHP-FPM is restarted.

systemctl restart php-fpm

After the restart, the OPcache shared memory is emptied and allocated again with the new settings.

php -i | grep -E '^opcache\.(enable|memory_consumption|revalidate_freq|validate_timestamps|jit) '
opcache.enable => On => On
opcache.jit => disable => disable
opcache.memory_consumption => 1024 => 1024
opcache.revalidate_freq => 60 => 60
opcache.validate_timestamps => On => On

The values printed by [php -i] are the settings of command-line PHP. On RHEL-family systems PHP-FPM reads the same [/etc/php.ini] and [/etc/php.d] files, so the values are the same.

Calling [opcache_get_status()] on the command line shows 0 cached scripts. Command-line PHP creates new shared memory on every run, so PHP-FPM's usage is checked by putting the file below in the web document folder and opening it in a browser.

<?php
header('Content-Type: text/plain');
$s = opcache_get_status(false);
print_r($s['memory_usage']);
print_r($s['opcache_statistics']);
print_r($s['interned_strings_usage']);
var_dump($s['cache_full']);

If [cache_full] is true, the shared memory or the keys are full. Delete this file right after checking.

On a server where three WordPress sites use the same PHP-FPM, 317MB of the 1024MB of shared memory was in use. There were 3,366 to 4,222 cached scripts per site, 11,358 in total.

OPcache stores scripts under their real path, and for files loaded through a relative path it creates one more key with the working directory attached, so the number of keys becomes larger than the number of scripts. With 11,358 scripts, the keys filled the limit of 16,229, and the 16MB string buffer also had no free space left.

When the keys are full, new scripts are not cached and are compiled on every request. In 5 minutes 21 seconds, cache misses rose from 18,408 to 36,027, but the number of cached scripts stayed at 11,358.

On a server that runs several WordPress sites or many plugins, set [opcache.max_accelerated_files] generously so that the key limit does not fill up. Setting it to 20000 gives an actual key limit of 32,531. If the string buffer is full, increase [opcache.interned_strings_buffer] as well.

🔢 FAQ & Recommended Content

That setting was removed in PHP 7.2, so in PHP 8 it has no effect even if it remains in php.ini. On a server that carried old settings over, you can delete this line.

OPcache stores compiled PHP code, while the Redis object cache stores database query results. Because they store different things, they can be used together.

On a server that runs PHP through PHP-FPM, OPcache works inside PHP-FPM, so restart only PHP-FPM. On a server that runs PHP as an Apache module (mod_php), restart Apache.

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 0   +   7   =