opcache.validate_timestamps decides if PHP checks the file's modification time at all. opcache.revalidate_freq decides how many seconds pass between those checks. If timestamps are off, PHP never checks and keeps serving the old bytecode until someone resets the cache. If the frequency is set high, PHP picks up your change eventually, but late enough that it looks broken.
This guide is about verifying that from the command line, not guessing. The steps are: prove the cached copy is the problem, read the settings that the serving PHP actually uses, reset the cache in the right place, and confirm the reset worked.
Rule out the other caches first
Find out which PHP actually serves the site
php -i | grep opcache over SSH shows the settings of the command-line PHP. The CLI usually has OPcache disabled (opcache.enable_cli is off by default) and often reads a different php.ini. It tells you nothing about the PHP-FPM, LSAPI, or Apache module that handles web requests. For the same reason, php -r 'opcache_reset();' from a shell only clears the CLI's own cache, not the one your site uses.
Find the handler first, then read its configuration and any per-pool overrides:
BASH
# Which PHP processes are running? (php-fpm, lsphp, or Apache with mod_php)
ps aux | grep -E '[p]hp-fpm|[l]sphp|[h]ttpd|[a]pache2'
# Debian/Ubuntu PHP-FPM: base settings plus pool overrides (adjust the version)
grep -rnE 'opcache[.](enable|validate_timestamps|revalidate_freq)' /etc/php/8.2/fpm/
# cPanel EA-PHP: php.ini, php.d and php-fpm.d live under the same root
grep -rnE 'opcache[.](enable|validate_timestamps|revalidate_freq)' /opt/cpanel/ea-php82/root/etc/
# Per-directory overrides inside the site itself
grep -nE 'opcache' /path/to/docroot/.user.ini /path/to/docroot/.htaccess 2>/dev/nullphp_admin_value[opcache.validate_timestamps] = 0. That line wins over php.ini for that pool, so do not stop at the first match. Paths vary by distro and panel, and RHEL-family systems typically use /etc/php.ini and /etc/php.d/. Some panels write per-domain overrides to their own locations. Treat the grep output as a strong hint, and confirm it with the next step.
Ask the running PHP what it has cached
PHP
<?php
// opcache-check-RANDOM.php: temporary diagnostic, delete after use
header('Content-Type: text/plain');
$target = __DIR__ . '/path/to/edited-file.php'; // the file you changed
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
echo 'Loaded php.ini: ', php_ini_loaded_file(), PHP_EOL;
foreach (['opcache.enable', 'opcache.validate_timestamps', 'opcache.revalidate_freq', 'opcache.restrict_api'] as $key) {
echo key, ' = ', var_export(ini_get(key), true), PHP_EOL;
}
if (!function_exists('opcache_get_status')) {
exit('OPcache extension is not loaded' . PHP_EOL);
}
$status = opcache_get_status(true);
if ($status === false) {
exit('OPcache is disabled or restricted (check opcache.restrict_api)' . PHP_EOL);
}
real = realpath(target);
if ($real === false) {
exit('Target file not found: ' . $target . PHP_EOL);
}
echo 'Target: ', $real, PHP_EOL;
echo 'File mtime on disk: ', date('c', filemtime($real)), PHP_EOL;
if (isset(status['scripts'][real])) {
s =status['scripts'][$real];
echo 'Cached: yes, hits = ', $s['hits'], PHP_EOL;
echo 'Cached copy based on mtime: ',
isset(s['timestamp']) ? date('c',s['timestamp']) : 'not tracked (validate_timestamps is off)',
PHP_EOL;
} else {
echo 'Cached: no', PHP_EOL;
}fpm-fcgi or litespeed. The settings show what is in effect for this site after every override. The comparison is the evidence. If the cached copy is based on an older mtime than the file on disk, or the timestamp is reported as not tracked and the file is cached, OPcache is serving the old version. If the file is not cached at all, or both times match, look elsewhere: a different file, a page cache, or an application-level cache.
Clear it in the right place
opcache_invalidate($real, true); to the diagnostic script to drop just the edited file, or call opcache_reset(); to clear the whole cache for that process group. Both only work if opcache.restrict_api allows calls from the script's path. If it does not, use the panel's PHP restart option if it has one, or ask your host. Under LiteSpeed, how long lsphp processes and their cache live depends on the server configuration, so check your own panel or server documentation instead of assuming.
Then verify the result instead of trusting the reset:
BASH
# Before: note the cached timestamp vs the file mtime
curl -s https://example.com/opcache-check-RANDOM.php
# Reset (root only; service name varies: php8.2-fpm, php-fpm, ea-php82-php-fpm)
sudo systemctl reload php8.2-fpm
# After: target should show 'Cached: no', or a timestamp matching the file mtime
curl -s https://example.com/opcache-check-RANDOM.php
# Remove the diagnostic script
rm /path/to/docroot/opcache-check-RANDOM.phpDecide whether timestamps should stay off
opcache.validate_timestamps is a deliberate performance choice. PHP skips a filesystem stat call on every include, which adds up on sites with many files. The trade-off is that every deploy has to include a cache reset. That works well when code only changes through a pipeline that reloads PHP afterwards. It works badly when people edit files over FTP or the file manager, or install plugins through the dashboard. WordPress updates fall into that last group too: core and plugin updates write new files, and with timestamps off they may not take effect until the next reset.
If nobody on the account deploys through a pipeline, the safer setup is usually timestamps on with a small opcache.revalidate_freq. That is close to the default behavior, and edits show up within a few seconds. If you want to keep timestamps off, put the reload or opcache_invalidate call in the deploy script itself, so it does not depend on someone remembering. Teams that switch releases with symlinks should also test that the switch is picked up. Path resolution caching can keep the old release running until PHP is reloaded, depending on the version and configuration.
The practical habit is simple. When an edit does not take effect, first prove which layer is stale with an uncached request. Then ask the web SAPI itself what OPcache holds, and reset it there instead of in the shell. Two curl requests before and after the reset tell you more than an afternoon of re-uploading the same file.