Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

OPcache Explained: PHP's Bytecode Cache

OPcache keeps compiled PHP code in shared memory. This saves processing time but demands care with deployments, restarts and on servers with several customers.

Last updated 5 min readPHP knowledge

In short: OPcache is PHP's built-in bytecode cache. According to the manual, it stores precompiled bytecode in shared memory so that PHP does not have to reload and compile scripts on every request. It has shipped with PHP since 5.5.0. OPcache makes PHP faster but does not replace an update: after a PHP patch, all PHP processes must restart; clearing the cache is not enough.

What exactly does OPcache do?

On every request, PHP has to read a script, break it down and compile it into opcodes, that is, the instructions for the virtual machine of the Zend Engine. For a CMS with hundreds of files this is a lot of work that is constantly repeated.

OPcache stores the result of this compilation in shared memory. All workers of a PHP-FPM service or all Apache processes with mod_php access it. If a file does not change, its bytecode is reused directly.

OPcache is loaded as a Zend extension, not as a normal extension:

; Linux (path depends on the package)
zend_extension=opcache.so
; Windows
; zend_extension=php_opcache.dll

According to the manual, OPcache should be loaded before Xdebug if both are active.

What matters is who owns the shared memory. With PHP-FPM, the master process creates it, and all workers of all pools of that service use it. With mod_php it belongs to the main Apache process. Restarting the service therefore clears the cache completely, whereas a single new worker does not. For the command line there is no shared memory across invocations, because every invocation is its own process; there, OPcache is off by default.

A rule of thumb is enough for decision-makers: OPcache makes sense on production servers almost always, but it requires a clear procedure for deployments and updates.

Which OPcache settings matter?

The following values come from the configuration page of the PHP manual. The "Changeable" column shows where a setting may be set: INI_SYSTEM only in php.ini or server configuration, INI_ALL anywhere.

Directive Default Changeable Meaning
opcache.enable 1 INI_ALL cache on or off, can only be switched off at runtime
opcache.enable_cli 0 INI_SYSTEM cache for the command line
opcache.memory_consumption 128 INI_SYSTEM size of the shared memory in MB
opcache.interned_strings_buffer 8 INI_SYSTEM memory for shared strings in MB
opcache.max_accelerated_files 10000 INI_SYSTEM maximum number of cached scripts
opcache.validate_timestamps 1 INI_ALL checks whether files have changed
opcache.revalidate_freq 2 INI_ALL check interval in seconds, 0 = on every request
opcache.restrict_api empty INI_SYSTEM allows API functions only from one path
opcache.validate_permission 0 INI_SYSTEM checks file permissions against the current user
opcache.validate_root 0 INI_SYSTEM prevents name collisions with chroot
opcache.file_cache empty INI_SYSTEM second cache level on disk

A typical production configuration might look like this. Check the values with your application beforehand:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.restrict_api=/var/www/admin-tools

Setting opcache.validate_timestamps=0 gains a little performance. But then OPcache no longer detects changed files at all, and every deployment needs a cache reset or a restart.

Observing the cache and clearing it selectively

OPcache comes with its own functions, including opcache_get_status(), opcache_get_configuration(), opcache_invalidate() and opcache_reset(). A short, read-only look at the fill level:

<?php
$status = opcache_get_status(false);
if ($status === false) {
    exit("OPcache ist nicht aktiv.\n");
}
printf("Gecachte Skripte: %d\n", $status['opcache_statistics']['num_cached_scripts']);
printf("Freier Speicher: %.1f MB\n", $status['memory_usage']['free_memory'] / 1048576);

On servers with several websites, not every application should be able to clear or read out the cache of all the others. This is what opcache.restrict_api is for: only scripts below the specified path may call the API functions.

JIT: the machine code compiler inside OPcache

Since PHP 8.0, OPcache contains a JIT compiler that translates opcodes into machine code. According to the JIT RFC, it is an almost independent part of OPcache and stores the generated code in a separate area of the shared memory. It is controlled via opcache.jit and opcache.jit_buffer_size. Since PHP 8.4.0, opcache.jit defaults to disable. When the JIT is worthwhile is explained in the article JIT and performance in PHP 8.

OPcache and security: what applies after PHP2Shell

OPcache is not a security tool, but it has consequences for security. Three points matter after PHP2Shell:

  1. A cache reset is not an update. opcache_reset() only clears cached scripts. The interpreter with the flaws CVE-2026-109618 and CVE-2026-109619 stays in memory until all processes have been restarted. How to do that is described under Restarting PHP-FPM and all workers.
  2. Injected code can live on in the cache. If a file was tampered with and later replaced, the old version can continue to be executed when the timestamp check is switched off. After a clean-up, a restart is therefore part of the job.
  3. Shared memory needs separation. On servers with several customers, separate PHP-FPM pools per website as well as opcache.validate_permission and opcache.restrict_api help. A directory for opcache.file_cache must be writable only by the PHP user.
Tip

After every update, check whether the start time of the PHP processes is later than the update. A full OPcache with a high hit rate is no proof that the new interpreter is running.

How does PHP SOS help?

  • Check the configuration: We read out the active OPcache configuration per operating mode and compare it with your application.
  • Secure deployments: We set up a fixed procedure for cache reset and restart so that no old code version keeps running.
  • Separate tenants: As part of server hardening we set up separate pools, restrict_api and suitable permissions.
  • Verify after updates: We use process start times to prove that the updated interpreter is really active.

Frequently asked questions

Is OPcache active by default?

OPcache has shipped with PHP since 5.5.0 and, according to the manual, runs with opcache.enable=1 as the default as soon as the extension is loaded. Whether it is loaded depends on your package or hosting provider. On the command line, however, OPcache is off by default (opcache.enable_cli=0).

Why do I still see the old code after a deployment?

OPcache probably checks for file changes only at intervals or not at all. With opcache.revalidate_freq it waits two seconds by default, with opcache.validate_timestamps=0 never. In that case only a cache reset or a restart of PHP-FPM or Apache helps.

Is opcache_reset() enough after a PHP update?

No. opcache_reset() only clears the cache of compiled scripts. A PHP update replaces the interpreter itself, and that is loaded in every running process. For that, all PHP processes must be restarted.

Is the JIT enabled in PHP 8.4?

According to the manual, opcache.jit has been set to disable by default since PHP 8.4.0, while opcache.jit_buffer_size is 64M. Before that it was the other way round: jit set to tracing, buffer set to 0. In both cases the JIT is not active without deliberate configuration.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help