OPcache Filesystem Considerations on Linux
AI generated
$
/etc
Linux · OPcache · PHP · Magento
OPcache Filesystem Considerations on Linux
Why the storage layer decides cache effectiveness

OPcache is usually treated purely as a PHP setting, yet its effectiveness depends directly on the underlying Linux filesystem. Inode behavior, mount options and network filesystems such as NFS can make the exact same OPcache configuration reliable on one server and unusable on another.

17 min read validate_timestamps · inodes · NFS · tmpfs PHP 8.x · Linux · Magento 2

1. Why OPcache is more than a PHP setting

OPcache stores compiled PHP bytecode in shared memory so the Zend compiler does not need to parse every PHP file on each request. That description is accurate, but it hides the fact that OPcache constantly interacts with the Linux filesystem to decide whether a cached file is still current. This is exactly where most performance problems arise that get wrongly classified as PHP problems, when the actual root cause sits in the storage layer.

Every interaction with OPcache filesystem behavior costs time, and that time differs massively between a local NVMe mount, a network filesystem and a containerized overlay filesystem. On a Magento server with thousands of PHP files in the vendor directory, even small per file latencies add up to noticeable delays if the wrong combination of OPcache configuration and filesystem is chosen.

This article focuses specifically on the OPcache filesystem interactions that are usually missing from standard tutorials: the cost of validate_timestamps, the behavior with symlink based deployments, the pitfalls of NFS, and the option of placing the secondary file cache on faster storage on purpose.

2. validate_timestamps and the cost of stat()

With opcache.validate_timestamps = 1, PHP checks on every request whether a file has changed since the last caching operation. This check is performed via a stat() system call that reads the modification time from the filesystem's metadata. On a local ext4 or XFS filesystem with a warm page cache, this call only costs microseconds, but on a slow or overloaded storage backend this time can grow noticeably, especially given the several thousand files Magento potentially includes per request.

The check interval is additionally controlled by opcache.revalidate_freq, which specifies the minimum number of seconds that must pass between two timestamp checks for the same file. For production Magento servers, where deployments go through a controlled process rather than direct file edits in a running system, opcache.validate_timestamps = 0 is the significantly more performant choice. Every stat() call is then eliminated entirely, and a manual cache reset after deployment replaces the automatic check.


; /etc/php/8.3/fpm/conf.d/10-opcache.ini
; Production setting: skip stat() calls entirely on the filesystem
opcache.enable = 1
opcache.validate_timestamps = 0
opcache.revalidate_freq = 0

; Staging/dev setting: check timestamps every 2 seconds per file
; opcache.validate_timestamps = 1
; opcache.revalidate_freq = 2

3. Inode based invalidation and symlink deployments

OPcache identifies cached files not only by path but internally also by the inode number of the file within the OPcache filesystem context. This becomes relevant with deployment strategies that use atomic symlink swaps, a common pattern in Capistrano style deployments where every release directory receives a fresh physical copy of the code and a current symlink is repointed afterwards.

Since every new release copy creates new inodes, even when the file content stays identical, OPcache reliably detects the new version when validate_timestamps is active. With validate_timestamps disabled, however, which is exactly what is recommended for maximum performance, the OPcache memory must be actively flushed after every symlink swap, otherwise PHP-FPM keeps serving bytecode from the old release directory even though the symlink already points to the new path.


#!/usr/bin/env bash
# deploy-with-opcache-reset.sh — atomic symlink swap plus OPcache invalidation
set -euo pipefail

RELEASE_DIR="/var/www/magento/releases/$(date +%Y%m%d%H%M%S)"
CURRENT_LINK="/var/www/magento/current"

echo "[DEPLOY] Extracting new release to $RELEASE_DIR"
mkdir -p "$RELEASE_DIR"
tar -xzf /tmp/release.tar.gz -C "$RELEASE_DIR"

echo "[DEPLOY] Swapping symlink atomically"
ln -sfn "$RELEASE_DIR" "$CURRENT_LINK"

# Critical: new inodes are invisible to a warm OPcache with
# validate_timestamps=0 until workers are recycled or reset explicitly.
echo "[DEPLOY] Resetting OPcache via FPM reload"
systemctl reload php8.3-fpm

echo "[DEPLOY] Done — release live at $RELEASE_DIR"

4. NFS and other network filesystems

When the Magento code directory is shared over NFS between multiple application servers, additional OPcache filesystem pitfalls arise that do not exist on local storage. Each server maintains its own OPcache in local shared memory, with no knowledge of what the other servers hold in their caches. If a file changes on the NFS share, servers only see it once their own validate_timestamps check detects the change, which of course does not happen at all when the check is disabled.

A second, more subtle problem: NFS caches metadata client side for a certain duration, controlled via mount options such as actimeo. Even with opcache.validate_timestamps active, a stat() call on an NFS mount can return stale metadata, because the NFS client never actually contacts the server but instead serves its local metadata cache. For multi server setups with a shared code directory, a central, coordinated deployment process with an explicit OPcache reset on all nodes is therefore mandatory, a simple touch on a file is not reliably sufficient on NFS.


#!/usr/bin/env bash
# reset-opcache-cluster.sh — invalidate OPcache on all app servers after
# a shared NFS deployment, since local stat() caching hides changes
set -euo pipefail

declare -a APP_SERVERS=("app1.internal" "app2.internal" "app3.internal")

for host in "${APP_SERVERS[@]}"; do
  echo "[RESET] Reloading PHP-FPM on $host"
  ssh -o BatchMode=yes "deploy@${host}" "sudo systemctl reload php8.3-fpm"
done

echo "[RESET] OPcache invalidated on all ${#APP_SERVERS[@]} nodes"

5. Moving opcache.file_cache onto tmpfs

Besides the shared memory cache, OPcache offers a secondary, file based cache via opcache.file_cache that survives a PHP-FPM restart. Without this option, the entire bytecode must be recompiled after every restart, which on a large Magento codebase means several extra seconds for the first requests after a restart while the shared memory cache is still empty.

The storage location of this file cache is a direct OPcache filesystem decision. If opcache.file_cache sits on a slow disk, warm up after a restart slows down accordingly. A tmpfs mount, a RAM backed filesystem set up via /etc/fstab, delivers significantly faster read and write access for this secondary cache, with the tradeoff that its content is completely lost on a server reboot and must be rebuilt once.


; /etc/php/8.3/fpm/conf.d/10-opcache.ini
; Secondary file-based cache survives php-fpm restarts (not server reboots)
opcache.file_cache = /var/cache/opcache-file
opcache.file_cache_only = 0
opcache.file_cache_consistency_checks = 1

# /etc/fstab — mount opcache file_cache directory on tmpfs (RAM-backed)
tmpfs /var/cache/opcache-file tmpfs rw,size=512M,mode=0755,uid=www-data,gid=www-data 0 0

# Apply without reboot:
# mkdir -p /var/cache/opcache-file
# mount /var/cache/opcache-file

6. Deployment strategies without cache breakage

The most stable OPcache filesystem strategy for Magento combines three elements: validate_timestamps = 0 for maximum performance in production, a controlled deployment process with an explicit systemctl reload after every symlink swap, and a secondary file cache on fast storage for short warm up times after planned restarts. This combination avoids both the performance cost of constant stat() calls and the risk of serving stale bytecode.

For blue green deployments or canary releases, where two code versions exist simultaneously under different paths, it must additionally be ensured that each PHP-FPM pool is exclusively assigned to one code version. If multiple code versions share the same PHP-FPM pool, the shared memory cache mixes bytecode entries from different releases, which can lead to inconsistent behavior when classes or functions are defined differently between the versions.

7. Monitoring OPcache status per filesystem

The opcache_get_status() function returns, among other things, the number of cache hits, cache misses and the current memory utilization. A high ratio of cache misses to hits usually indicates that the OPcache memory is dimensioned too small, or that constant invalidations are happening due to OPcache filesystem behavior, for example a too short revalidate_freq combined with a slow storage backend.

A simple monitoring script that periodically queries opcache_get_status() via an internal diagnostic endpoint reveals whether opcache.memory_consumption is sized adequately. If the number of restarts due to a full cache increases regularly, either the allocated memory needs to be raised or opcache.max_accelerated_files needs to be adjusted to the actual number of files in the vendor directory.

8. Common mistakes with OPcache and filesystems

The most common mistake is leaving validate_timestamps at 1 in production out of concern that disabling the timestamp check might cause problems. In fact, this constant check on a high traffic Magento server with thousands of PHP files introduces measurable overhead without providing any benefit in a controlled deployment process, since files should not be edited directly in a running system anyway.

The second common mistake concerns multi server setups: after a deployment, only the most recently updated server gets reloaded, while other nodes in the cluster continue serving old bytecode from their local shared memory cache. Without a central deployment step that triggers systemctl reload on all application servers simultaneously, inconsistent responses arise depending on which server handles a request, an issue that is often only identified as an OPcache problem after a lengthy investigation.

9. Filesystem options compared

The choice of filesystem and mount options for the Magento codebase has a direct impact on OPcache filesystem performance, especially during warm up and invalidation.

Setup stat() latency Multi server consistency Recommendation
Local ext4/XFS Very low Independent per server, needs reload Standard for single server setups
NFS share High, metadata cache delays Requires coordinated reset Only with a central deploy script
tmpfs for file_cache Minimal, RAM backed Per server, content lost on reboot For fast warm up after restarts
Overlay FS (container) Variable, depends on storage driver Isolated per container Size memory_consumption generously

For production Magento servers, the combination of local ext4 or XFS storage for the code, disabled timestamp checking and a tmpfs based secondary file cache is the most stable OPcache filesystem configuration. NFS setups are feasible, but they strictly require a coordinated deployment process that invalidates the cache synchronously across all involved nodes.

Mironsoft

Linux server tuning and performance optimization for Magento

Is your server serving stale code after every deployment?

We review your OPcache configuration together with the filesystem, set up coordinated cache invalidation for multi server setups, and optimize warm up time after restarts.

OPcache audit

Review and optimize validate_timestamps, memory_consumption and file_cache

Deployment automation

Set up coordinated cache invalidation across all application servers

Storage consulting

Find the right filesystem and mount strategy for your Magento stack

10. Summary

The OPcache filesystem interaction is reduced to a single setting in most tutorials, yet the underlying storage significantly co determines actual performance. Disabled timestamp checking saves every stat() call in production, but demands a controlled deployment process with an explicit cache reset in return. Symlink based deployments create new inodes that remain invisible without an active reset.

NFS setups add extra complexity through client side metadata caching, which can undermine even an active timestamp check. A secondary file cache on tmpfs significantly shortens warm up time after planned restarts. Anyone who understands these OPcache filesystem relationships avoids both unnecessary performance loss and the risk of serving stale code to customers.

OPcache Filesystem Considerations — The Essentials at a Glance

Timestamps

validate_timestamps = 0 in production, with an explicit reset after every deployment via reload.

Symlink deployments

New inodes remain invisible to the shared memory cache without an active reset.

NFS pitfalls

Client side metadata caching requires a coordinated reset across all application servers.

file_cache

Move it onto tmpfs for minimal warm up time after PHP-FPM restarts.

11. FAQ: OPcache Filesystem Considerations on Linux

1Why is OPcache dependent on the filesystem?
stat() calls check file metadata, whose latency varies greatly by storage type.
2Should validate_timestamps be enabled in production?
No, disable it and reset explicitly after every deployment instead.
3What happens with symlink deployments?
New inodes stay invisible until the cache is explicitly flushed via reload.
4Why is NFS problematic?
Client side metadata caching can return stale timestamps even with active checking.
5What is opcache.file_cache?
A secondary cache that keeps bytecode across FPM restarts.
6Should file_cache live on tmpfs?
Yes, for minimal latency, with content lost on reboot.
7How do you invalidate OPcache in a cluster?
Central deployment script with simultaneous reload on all nodes.
8What does opcache_get_status() show?
Hits, misses and memory utilization as diagnostic values.
9Is touch enough for NFS updates?
No, an explicit reload across all nodes is more reliable.
10What is the most stable configuration?
Local ext4/XFS, disabled timestamp checking, tmpfs for the secondary cache.