PHP OPcache Not Clearing? Why Code Changes Don't Show
You edit a PHP file, upload it, refresh the browser - and nothing changes. Not a cache-busting problem in the browser, not a CDN, not even WordPress's own cache plugins. The file on disk is correct. The server is just running an older, compiled copy of it from memory. That's PHP OPcache, and it trips up more deployments than people realize.
The Symptom
You'll usually see one of these:
- You fix a bug in
functions.phpor a plugin file, but the old (broken) behavior keeps happening. - A file you deleted from the server still seems to execute.
- After a Git deploy or SFTP upload, the site looks fine in File Manager but wrong in the browser - and it stays wrong for minutes or hours.
- Clearing your browser cache, disabling WP Super Cache/LiteSpeed Cache, and even purging Cloudflare does nothing.
If you've ruled out browser and page caching and the problem is still there, OPcache is the next place to look.
What OPcache Actually Does
PHP is an interpreted language, but interpreting the same file from scratch on every request is wasteful. OPcache (built into PHP since 5.5, and enabled by default on almost every cPanel and VPS PHP build) compiles your PHP source into bytecode once, then keeps that bytecode in shared memory. The next request for the same file skips parsing entirely and runs the cached bytecode directly. On a busy WordPress or Laravel site this can cut PHP execution time by 30-50%.
The catch: OPcache decides whether a file has changed by checking its path and, depending on config, its modification timestamp - not by re-reading the content on every request. If that check is disabled or the timestamp doesn't move the way OPcache expects, it keeps serving the old bytecode even though the file on disk is new.
Why It Goes Stale
1. opcache.validate_timestamps is off
Many hosts (including some optimized cPanel PHP-FPM pools) set opcache.validate_timestamps=0 in production for a small performance gain. That tells OPcache: never check the file again, ever, until the cache is manually flushed or PHP-FPM restarts. Great for performance, brutal for anyone deploying code without knowing it.
2. The check interval is too long
Even with timestamp validation on, opcache.revalidate_freq controls how often (in seconds) OPcache re-checks a file. The default is 2, but some hosts raise it to 60 or higher. Your edit is live - just not for up to a minute.
3. Deploy tools that preserve mtimes
Git checkouts, rsync with -a, and some CI/CD pipelines can preserve the original file modification time instead of setting it to "now." If the new file's mtime is identical to (or older than) what OPcache already cached, it isn't treated as changed.
4. Each PHP-FPM worker has its own cache
OPcache's shared memory is per PHP-FPM pool, and on multi-server or load-balanced VPS setups, per server. Clearing it on one node doesn't touch the others - you'll see the fix "flicker" in and out depending on which backend serves the request.
How to Confirm OPcache Is the Cause
Before changing anything, verify it's actually OPcache and not something else eating your edit (a caching plugin, a reverse proxy, or - very commonly - editing the wrong file because a staging copy exists alongside the live one).
<?php
if (function_exists('opcache_get_status')) {
$status = opcache_get_status(false);
echo 'Enabled: ' . var_export($status['opcache_enabled'], true) . PHP_EOL;
echo 'Cached scripts: ' . $status['opcache_statistics']['num_cached_scripts'] . PHP_EOL;
} else {
echo 'OPcache is not loaded for this SAPI.';
}
Drop that in a throwaway file like opcache-check.php, load it in the browser, then delete it once you're done - don't leave diagnostic scripts sitting on a live site. If opcache_enabled is true and the file you edited shows up under cached scripts with an old timestamp, that confirms it.
The Fix
Fastest: restart PHP-FPM
Restarting the PHP-FPM service clears its entire OPcache along with it - blunt, but reliable. On a VPS running WHM/cPanel with PHP-FPM:
systemctl restart php-fpm
# CloudLinux/cPanel with per-domain PHP-FPM pools:
/scripts/restartsrv_apache_php_fpm
On a plain Nginx + PHP-FPM VPS, restart the specific version's service, e.g. systemctl restart php8.2-fpm. This causes a brief request queue, not downtime, on a properly configured pool.
Shared hosting without root: use MultiPHP INI Editor
If you're on shared cPanel hosting without shell access, you can't restart the daemon directly, but you can toggle a setting that forces a reset:
- In cPanel, open MultiPHP INI Editor.
- Select the domain, switch to Editor Mode.
- Set
opcache.enable=0, save, reload the site once to flush, then set it back toopcache.enable=1.
It's a hack, but it works when you have no other lever to pull. For a permanent fix, ask your host (or set it yourself if you manage the server) to change opcache.validate_timestamps=1 and opcache.revalidate_freq=0 - or at least 2 - in the PHP config for that domain.
Programmatic reset (good for deploy scripts)
If you deploy via a script or CI pipeline, call opcache_reset() as the last step of the deploy, ideally from a small endpoint you trigger over HTTP so it runs in the same PHP-FPM pool as your live traffic:
<?php
// deploy-cache-clear.php - protect this behind auth or delete after use
if (function_exists('opcache_reset')) {
opcache_reset();
echo 'OPcache cleared.';
}
WordPress-specific: don't forget the object cache too
If the site also runs Redis or Memcached object caching, a stale template or plugin bug can look identical to an OPcache issue but actually be a stale object cache entry. Clear both when troubleshooting:
wp cache flush
wp cli cache clear
Quick Reference
| Environment | How to clear OPcache |
|---|---|
| VPS, root access, PHP-FPM | systemctl restart php-fpm (or version-specific service) |
| cPanel/WHM VPS, CloudLinux | /scripts/restartsrv_apache_php_fpm |
| Shared cPanel hosting, no shell | Toggle opcache.enable off/on in MultiPHP INI Editor |
| Deploy script / CI pipeline | Call opcache_reset() at the end of the deploy |
| Multi-server / load balanced | Repeat the clear on every backend node, not just one |
Prevention
- Keep
opcache.validate_timestamps=1in any environment where code changes often, and setopcache.revalidate_freqlow (0-2) rather than disabling validation for a marginal speed gain. - Make deploy scripts touch the file mtime explicitly (
touch) or callopcache_reset()as the final step, rather than trusting rsync/Git to bump timestamps the way you expect. - If you run staging and production on the same server, use separate PHP-FPM pools so an OPcache flush on staging never touches production, and vice versa.
- Document which restart command clears cache on your specific stack - "OPcache is stale" is a much faster diagnosis when the fix is already written down.
Stale OPcache is one of those bugs that looks like everything except what it is - a caching plugin issue, a CDN issue, a "did I even save the file" moment. Once you know to check opcache_get_status() first, it stops costing you twenty minutes of confused re-uploading.
Frequently asked questions
Does clearing OPcache cause downtime?
No. Restarting PHP-FPM briefly queues incoming requests for a fraction of a second while workers respawn - visitors won't see an error page on a properly configured pool. It's not the same as restarting Apache or Nginx.
Why does my fix work for a minute, then break again?
That's usually opcache.revalidate_freq set to a high value combined with a load-balanced setup - one PHP-FPM worker picks up the change, others haven't revalidated yet, so requests flip between old and new behavior until every worker catches up.
Is OPcache the same as a WordPress caching plugin?
No. WordPress caching plugins (WP Super Cache, LiteSpeed Cache, etc.) cache rendered HTML output. OPcache caches compiled PHP bytecode at the server level, underneath WordPress entirely. Clearing one doesn't clear the other.
Can I just disable OPcache to avoid this?
You can, but you shouldn't leave it off - it's one of the biggest free performance wins PHP has. Fix the revalidation settings instead of disabling it outright; the goal is fast AND fresh, not one or the other.
How do I check if OPcache is even enabled on my hosting?
Run phpinfo() and search for 'opcache', or use the opcache_get_status() snippet in this article. Most cPanel shared hosting and all Getwebup VPS plans have it enabled by default.