rm. A few minutes later, your web server access logs show remote attackers continuing to hit the exact same URL, receiving clean HTTP 200 responses and executing arbitrary commands. The file is completely absent from the file system, yet the backdoor behaves as if nothing changed.
This behavior routinely causes confusion during server cleanup. It is not caused by ghost processes or rootkits hiding directory entries; it is standard PHP OPcache behavior. When OPcache is active, PHP does not run files directly from disk on every HTTP hit. It compiles PHP code into low-level instructions called bytecode and stores that bytecode in shared memory. Here are the most common misconceptions about how OPcache handles removed files and how to clear them reliably.
Myth 1: Deleting a file from disk immediately stops execution
Myth 2: OPcache checks disk status on every single hit
opcache.validate_timestamps, but it does not run a synchronous disk check on every HTTP request.
Instead, OPcache relies on opcache.revalidate_freq, which specifies how many seconds must elapse before PHP checks whether a file has changed. If this value is set to a higher interval or disabled altogether for performance in production environments, PHP skips disk queries entirely. Furthermore, when opcache.validate_timestamps is set to zero (a common configuration on high-traffic or immutable server deployments), PHP will never check the disk for updates or deletions after the initial compile. The bytecode persists in memory indefinitely until the PHP master process restarts or receives a cache reset signal.
Even when revalidation is enabled with a short interval, an attacker running continuous loops against a webshell can keep interacting with the script until the check window lapses. In some edge cases involving path resolution caching, deleted files may linger in worker memory longer than expected.
Myth 3: Running opcache_reset() via terminal fixes the web server
php -r 'opcache_reset();'. The terminal prints no errors, giving the impression that the cache has been flushed. Yet testing the deleted webshell URL still yields an active HTTP 200 response.
This happens because the command-line interface (CLI) and the web process (PHP-FPM, Apache mod_php, or LiteSpeed) operate in entirely different memory segments. Shared memory segments are tied to the parent process pool. When you invoke php on the command line, your operating system spawns a transient, short-lived PHP process with its own empty OPcache allocation. Running opcache_reset() inside that session flushes the CLI memory segment, which was already created milliseconds prior, and leaves the persistent memory segment of your web server untouched.
To invalidate OPcache inside the web environment, the reset command must be evaluated by the actual web worker, or the master PHP service must be reloaded.
Myth 4: A full server reboot is necessary to flush malicious memory
BASH
# Check the active PHP-FPM service name
systemctl list-units --type=service | grep fpm
# Gracefully reload the service without dropping active connections
systemctl reload php8.2-fpmopcache_invalidate('/path/to/deleted/webshell.php', true); and call it once through curl. That tells the web worker directly to purge the bytecode matching that path from shared memory, after which you immediately remove the helper script.
Cleaning infected files from the file system is only the first step in remediation. Until you account for the persistence of shared memory in production web stacks, your application may remain actively compromised long after your scanner reports a clean disk.