After a PHP deploy on servers with opcache.validate_timestamps=0, the site keeps serving the old code; why, and how should the deploy clear OPcache?
answer
- the cache belongs to the server process
- CLI opcache_reset() hits a different cache
- reset is scheduled, not instant
- opcache.restrict_api guards the functions
- recompile burst after every reset
basics
~20 sWith validation off, OPcache never rereads changed files, so old compiled scripts keep running. Clear the cache inside its owner: call opcache_reset() through the web server's PHP, or restart or reload PHP-FPM; a CLI opcache_reset() cannot reach it.
solid answer
~50 s`opcache.validate_timestamps=0` tells OPcache never to check the disk, so new files are invisible until the cache is cleared. The classic mistake is adding `php -r 'opcache_reset();'` to the deploy script: that runs in a separate CLI process whose cache — absent with the default `opcache.enable_cli=0`, in which case the call just returns `false` — is not the PHP-FPM master's shared memory. Working options: restart or gracefully reload PHP-FPM, which recreates the segment; or call `opcache_reset()` from inside the pool, through an internal endpoint or a FastCGI request, locked down with `opcache.restrict_api`. A reset is **scheduled**: the cache is emptied once no request is using it, so in-flight requests finish on the old code. Expect a short CPU burst while every worker recompiles, and remember that long-running CLI workers keep their old code in memory until they are restarted.
code
php · 10 lines<?php
// /srv/ops/opcache-reset.php, reachable only from the deploy host;
// php.ini: opcache.restrict_api=/srv/ops/
declare(strict_types=1);
header('Content-Type: application/json');
echo json_encode([
'reset' => opcache_reset(), // true = restart scheduled
'pending' => opcache_get_status(false)['restart_pending'] ?? null,
]);go deeper
Recall that with validation off, deployed files are ignored until OPcache is reset or PHP-FPM is restarted.
Explain why a CLI call to opcache_reset() cannot reach PHP-FPM's shared memory, and list the ways that do.
Design the deploy sequence: release directory, switch, in-server reset or reload, worker restarts, verification, and a staggered reset to absorb the recompile burst.
Choose between in-place resets and replacing instances per release, trading deploy speed against cold-cache warm-up and operational simplicity.
## Why the old code keeps running With `opcache.validate_timestamps=0`, OPcache compiles each file once and then serves the cached opcodes forever; it never compares the file on disk with what it has. Replacing the files therefore changes nothing for a running server. That is the intended behaviour of the setting — the deploy is expected to clear the cache — and the symptom appears when nobody built that step. ## Where the cache lives The cache is a **shared memory segment created by the server process at startup** and used by the processes started from it. Under PHP-FPM that is the master process and the workers it forks. Everything that talks to the cache must run inside that process tree. That explains the most common failed fix: ```bash php -r 'opcache_reset();' # in the deploy script: does not touch PHP-FPM's cache ``` A `php` command is a new CLI process. With the default `opcache.enable_cli=0` it has no cache at all and `opcache_reset()` returns `false`; with the CLI cache enabled, it resets its own private segment, which disappears when the command exits. PHP-FPM's cache is untouched either way. ## Ways that work | Approach | How it clears the cache | Watch out for | |---|---|---| | Restart or gracefully reload PHP-FPM | the master starts again with a new segment | brief capacity dip; the reload mechanics belong to the FPM configuration | | `opcache_reset()` from inside the pool | an internal URL or a FastCGI request runs the call in a worker | must be protected; restrict it with `opcache.restrict_api` | | `opcache_invalidate($file, true)` per changed file | drops single entries | misses deleted and renamed files and regenerated autoload maps | | New server or container per release | the new instance starts with an empty cache | none for OPcache; the cache warms on first traffic | `opcache.restrict_api` takes a path prefix; OPcache API functions called from scripts outside it return `false` with a warning, so only the reset script under that prefix can clear the cache. ## What opcache_reset() actually does 1. It **schedules** a restart of the cache and returns `true`, or `false` if OPcache is not active in this process or a restart is already pending or in progress. 2. The cache is emptied when no process is using it. Requests already running finish on the old code; new ones compile the new code. 3. If some process holds the cache for too long, OPcache forces the restart after `opcache.force_restart_timeout` seconds (default `180`) by killing the processes that still hold it. 4. `opcache_get_status()` reports `restart_pending` and `restart_in_progress`, and counts resets in `opcache_statistics.manual_restarts`. ## Side effects to plan for - **Recompile burst.** After a reset every worker compiles the files it needs on its next requests. On a busy server this shows as a CPU spike and slower responses for a few seconds. Resetting servers one at a time behind the load balancer smooths it. - **Symlinked release directories.** Many deploys switch a `current` symlink to a new release directory. Cached entries and PHP's realpath cache can keep resolving the symlink to the previous release until they are cleared, so the switch still needs a reset or reload; some setups pass the resolved release directory to PHP so every release has distinct paths. - **Several servers.** Each host has its own cache. A reset must reach every host behind the load balancer, and a host that missed it keeps serving the previous release indefinitely. - **Long-running PHP processes.** A queue consumer or daemon started before the deploy has the old classes loaded in its own memory. No OPcache operation changes that; those processes must be restarted. ## A deploy order that holds up 1. Upload the new release to a new directory. 2. Run migrations and build steps (autoload maps, caches). 3. Switch the web root to the new release. 4. Clear OPcache inside the server — reload PHP-FPM, or call the protected reset endpoint. 5. Restart long-running workers. 6. Check `opcache_get_status()` or a version endpoint to confirm the new code is live.
- Why is opcache_invalidate() on the changed files not enough for a typical deploy?It only drops entries for the paths you pass. Deleted files stay cached, renamed classes leave stale entries, and regenerated autoload maps or config files are easy to miss. A full `opcache_reset()` or a PHP-FPM reload clears everything at once, which is safer for a release.
- Is it safe to reset OPcache while requests are in flight?Yes. `opcache_reset()` only schedules the restart; the cache is emptied when no process is using it, so running requests finish with the code they started with. The cost is the recompile burst afterwards, not broken requests.
- Why can a queue worker still run old code after OPcache was reset?A long-running PHP process loads its classes once, into its own memory, when it starts. Clearing OPcache affects future compilations only; the worker keeps executing what it already loaded until it is restarted.
saying these in an interview costs you the question
- Running php -r 'opcache_reset();' in the deploy clears PHP-FPM's cache
- opcache_reset() empties the cache instantly, breaking running requests
- OPcache notices changed files eventually even with validation off
- Resetting OPcache also restarts long-running queue workers
- opcache_invalidate() on changed files covers deleted and renamed ones