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:
- 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. - 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.
- Shared memory needs separation. On servers with several customers, separate PHP-FPM pools per website as well as
opcache.validate_permissionandopcache.restrict_apihelp. A directory foropcache.file_cachemust be writable only by the PHP user.
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_apiand 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.