Tuning the PHP Realpath Cache: Fewer Stat Calls, Faster Includes
AI generated
<?php
8.4
PHP · Realpath Cache · Filesystem · Performance
Tuning the PHP Realpath Cache
Fewer stat calls, noticeably faster includes

Every require, every include and every autoloader resolution in PHP goes through a path resolution step that, without a sufficiently sized realpath cache, triggers filesystem access again on every single request. In projects with thousands of classes and deep directory trees this adds up to an overhead that a few lines of configuration can almost completely eliminate.

13 min read realpath_cache_size · realpath_cache_ttl PHP 8.x

1. What the realpath cache is and what problem it solves

The realpath cache is an internal Zend Engine cache that stores resolved, absolute file paths together with information about existence, type and the target file behind symlinks. Whenever PHP has to convert a relative path, a path with .. segments or a symlink into a canonical, absolute path, for example on every require, include or autoloader resolution, the engine first checks this cache before actually querying the filesystem.

Without this cache, PHP would need to send one or more stat() system calls to the operating system for every single path resolution, one for each segment of a directory path, just to check its existence and type. For a deeply nested namespace path like vendor/magento/module-catalog/Model/ResourceModel/Product.php, that potentially means seven or eight individual system calls for a single class file, and that again on every request if the realpath cache is undersized or expires too quickly.

The realpath cache addresses exactly this: once a path is resolved and stored in the cache, PHP returns the result directly from memory on every further request within the configured validity period, with no renewed contact with the filesystem. For projects with many classes and deep directory structures, as is typical in Magento or Symfony, a correctly sized realpath cache makes the difference between thousands of unnecessary system calls per request and practically none.

2. How PHP resolves file paths without a sufficient cache

To understand why the realpath cache is so effective, it helps to look at the process without it. For every path that needs resolving, PHP walks the filesystem segment by segment: for /var/www/html/vendor/foo/bar/Baz.php it first checks whether /var/www exists and is a directory, then /var/www/html, then /var/www/html/vendor, and so on until the full path is resolved. Each of these checks is a separate system call with a context switch between the PHP process and the operating system kernel.

Symlinks complicate this process further, because every symlink in the path must itself be resolved, which theoretically triggers additional filesystem access for the symlink's target. In containerized environments with mounted volumes or overlay filesystems, these system calls are also often slower than on a local filesystem without a virtualization layer, which makes the effect of a missing or too small realpath cache particularly noticeable in Docker environments.


declare(strict_types=1);

// Without an effective realpath cache, resolving this path repeatedly
// triggers multiple filesystem stat() calls per request
$path = __DIR__ . '/../../vendor/magento/module-catalog/Model/Product.php';

$start = hrtime(true);
$resolved = realpath($path);
$elapsed = (hrtime(true) - $start) / 1_000_000;

printf("Resolved: %s (%.4f ms)\n", $resolved, $elapsed);

// Inspect current cache usage right after resolution
$info = realpath_cache_size();
printf("Realpath cache currently uses %d bytes\n", $info);

3. Configuring realpath_cache_size and realpath_cache_ttl

Two directives in php.ini control the realpath cache directly: realpath_cache_size sets the total amount of memory available for cached path entries, often only 4096K, four megabytes, by default. For large projects with many thousands of files, this default is frequently insufficient, the cache fills up, older entries get evicted, and the filesystem access it was supposed to avoid happens again.

realpath_cache_ttl determines how long a once resolved path stays valid in the cache before it is checked against the filesystem again, 120 seconds by default. For production environments with a stable deployment process, where file paths do not change during runtime between deployments, it is worth raising this value considerably, since a frequently expiring cache in production has no practical benefit but causes unnecessary system calls.


; php.ini — realpath cache tuning for a large, stable production project
; Default is often only 4096K, far too small for projects with
; thousands of classes across deep vendor/ directory trees
realpath_cache_size = 16M

; Default TTL is 120 seconds — for stable production deployments
; where paths never change between deploys, a longer TTL avoids
; unnecessary re-validation against the filesystem
realpath_cache_ttl = 600

; Combine with an aggressive OPcache configuration for maximum effect
opcache.validate_timestamps = 0

A value of 16M for realpath_cache_size is sufficient for the vast majority of projects, even with tens of thousands of files in the vendor directory. Important: the value is specified as a string with a unit, 16M for megabytes, a plain number is interpreted as bytes, which is a common misconfiguration that renders the realpath cache effectively useless.

4. Interplay with OPcache and Composer autoloading

The realpath cache works closely together with OPcache but solves a different problem: OPcache caches compiled bytecode for a file once its path is known, while the realpath cache speeds up exactly this path resolution step itself, before OPcache can even take effect. With opcache.validate_timestamps disabled, OPcache relies entirely on once resolved paths, which makes a sufficiently sized realpath cache even more important, since it acts as the first stage of path resolution.

Composer autoloading benefits especially strongly from the realpath cache, because the generated class autoloader performs a path resolution for every unknown class, either directly through the classmap or through PSR-4 prefix resolution followed by a file_exists() check. For a typical Magento or Symfony request loading hundreds of classes, these resolutions quickly add up to a three digit number of path checks, which with a working realpath cache are almost entirely served from memory instead of hitting the filesystem.


declare(strict_types=1);

// Simulate a Composer-style classmap lookup with realpath caching in effect
final class ClassResolutionBenchmark
{
    /**
     * Resolve a batch of class file paths and report cache effectiveness.
     *
     * @param array<int, string> $relativePaths
     * @return void
     */
    public static function run(array $relativePaths): void
    {
        $before = realpath_cache_size();

        foreach ($relativePaths as $path) {
            realpath($path); // triggers cache lookup or filesystem stat
        }

        $after = realpath_cache_size();
        printf("Cache grew by %d bytes for %d paths\n", $after - $before, count($relativePaths));
    }
}

5. Measuring and observing cache usage yourself

PHP provides two functions to inspect the current state of the realpath cache directly: realpath_cache_size() returns the currently occupied memory in bytes, realpath_cache_get() returns an array with all currently cached paths, including their resolved target paths and the timestamp of the last update. These functions are excellent for checking during development whether the configured realpath_cache_size value is actually sufficient for your project.

A practical test: if realpath_cache_size() approaches the configured maximum at the end of a typical, complex request, the cache is undersized and should be increased. If the occupied size stays well below the maximum, the configuration is sufficient, and a further increase brings no additional benefit but unnecessarily costs memory per PHP worker process, which can become relevant with many parallel workers.

For continuous monitoring in production, a simple health check endpoint that periodically checks realpath_cache_size() against the configured limit and raises a warning at near full utilization is useful, before actual performance problems from cache eviction occur.

6. Docker, symlinks and container peculiarities

In containerized environments with mounted bind mounts or overlay filesystems, common in local development with Docker, filesystem access is generally slower than on a native filesystem, because every access additionally passes through the host's virtualization layer. This is exactly the scenario where a correctly sized realpath cache shows particularly noticeable effects, since every avoided filesystem access saves disproportionately more time compared to a native Linux filesystem.

Symlinks, as used by Composer for some package installations or by developers for local package overrides via a path repository, further increase the number of necessary resolution steps, because every symlink itself must be resolved before the actual target path is known. The realpath cache stores both the symlink path and the resolved target path, so repeated access through the same symlink is also sped up once the first access has resolved the full chain.

7. Deployment strategies without cache invalidation issues

A long lived realpath cache with a high realpath_cache_ttl can cause problems with certain deployment strategies, especially symlink based deployments where a current symlink is repointed to a new release directory after every release. If the old, resolved path remains in the realpath cache of an already running PHP-FPM worker process, that worker may serve code from the old release for the duration of the configured TTL, while new workers already use the new release.

The reliable solution is a full PHP-FPM reload after every deployment, which restarts all worker processes and thereby also fully resets their realpath cache and OPcache. A plain cache flush via signals is often not enough for symlink based deployments, because the realpath cache itself has no explicit flush command and is only cleared through TTL expiry or a process restart.

8. Common pitfalls and misconfigurations

The most common mistake is simply leaving the realpath cache at its far too low default value, even though your own project has thousands of files in the vendor directory. A too small cache evicts older entries as soon as new paths are added, which with cyclical access patterns leads to constant rebuilding, so the cache remains practically ineffective despite being actively configured.


#!/usr/bin/env bash
# Quick diagnostic: check the currently configured realpath cache settings
php -i | grep -i realpath

# realpath_cache_size => 4096K => 4096K    (too small for large projects)
# realpath_cache_ttl => 120 => 120         (fine for dev, low for stable prod)

# Verify actual usage from within a running application context
php -r 'var_dump(realpath_cache_size());'

A second pitfall concerns development environments: a very high realpath_cache_ttl value in local development causes newly created files not to be found immediately, because a previously cached non existence result is still valid. For local development the default of 120 seconds should be kept or even reduced, raising it only pays off for stable production environments with unchanging deployment.

9. Configurations compared directly

A direct comparison of typical configurations shows which settings fit which scenario.

Scenario realpath_cache_size realpath_cache_ttl Recommendation
Default install 4096K 120 Too small for large projects
Local development 8M 120 New files visible quickly
Stable production 16M 600 Maximum reuse
Docker with bind mount 16M 600 Especially effective due to overlay FS

The comparison shows: for production environments it is almost always worth raising both values considerably above the default, while in local development a moderate compromise between performance and immediate visibility of new files makes more sense.

Mironsoft

PHP performance tuning, OPcache and realpath cache configuration

Looking for unnecessary filesystem access in your PHP application?

We analyze autoloading and include behavior in large codebases, size realpath cache and OPcache to match project scale, and set up stable deployment processes free of cache invalidation issues.

Cache sizing

Measuring and setting realpath_cache_size and realpath_cache_ttl to match project scale

Deployment hardening

PHP-FPM reload strategies against stale realpath cache entries after releases

Docker optimization

Aligning realpath cache and OPcache for bind mount and overlay filesystems

10. Summary

The realpath cache is an unassuming but powerful building block of PHP performance, speeding up path resolution for require, include and autoloading by avoiding repeated filesystem access. realpath_cache_size and realpath_cache_ttl are the two decisive settings, with the four megabyte default almost always too small for larger projects. Combined with OPcache and disabled timestamp validation, the realpath cache reaches its full effect.

Especially in Docker environments with bind mounts and in projects with deep directory structures like Magento or Symfony, a deliberate configuration well above the default pays off considerably. It remains important to shape deployment processes so that a PHP-FPM reload after every release fully resets the realpath cache, avoiding stale paths after symlink based deployments.

PHP Realpath Cache, the Key Takeaways

Default is almost always too small

4096K is not enough for large projects, 16M is sufficiently sized for most cases.

Adjust TTL to deployment stability

Short TTL for local development, much longer TTL for stable production environments.

Measure usage instead of guessing

realpath_cache_size() and realpath_cache_get() show whether the configured size is actually sufficient.

PHP-FPM reload after deployment

Prevents workers with a long TTL from still holding paths from an old release in the cache.

11. FAQ: Tuning the PHP Realpath Cache

1What is the realpath cache?
Stores resolved file paths so require, include and autoloading do not need to query the filesystem every time.
2How large should realpath_cache_size be?
16M is sufficient for most large projects, the default of 4096K is usually too small.
3What does realpath_cache_ttl do?
Determines how long a cached path stays valid, default is 120 seconds.
4How do I detect a too small cache?
realpath_cache_size() shows current usage, close to the maximum means undersized.
5Why does it matter in Docker?
Bind mounts and overlay filesystems make file access slower, the cache avoids that expensive access.
6How does it relate to OPcache?
It resolves paths before OPcache looks up bytecode by path, especially important with timestamp validation disabled.
7Can a long TTL disrupt deployments?
Yes, for symlink based deployments a PHP-FPM reload after every release avoids stale cache entries.
8Can I flush the cache manually?
There is no flush command, only TTL expiry or a process restart clear it.
9Should I raise it locally too?
Raise the size moderately, keep the TTL low so new files are found immediately.
10Does a very high size help more?
No, once the cache holds all used paths, more size brings no additional benefit.