...

PHP Realpath Cache: An Underrated Boost for PHP Performance

The often-overlooked way to speed up PHP requests is called PHP Realpath Cache: It stores resolved paths in memory and reduces costly file system lookups during `include` and `require` operations. In projects using Symfony, Laravel, or a large WordPress setup, I improve performance with a clean Realpath configuration by Performance measurable, and keep the number of system calls per request significantly lower.

Key points

  • Simpler Hebel: Realpath stores path resolutions and reduces the number of file system accesses.
  • Per Worker: Each PHP-FPM process maintains its own Realpath cache.
  • Size Note: A cache that is too small causes thrashing and slows down requests.
  • TTL Controls timeliness: Long TTL for stable deployments, short TTL for symlinks and secrets.
  • Monitoring: realpath_cache_get()/size() show utilization and gaps.

Exactly What the Realpath Cache Does

Every time an `include`, `require`, or `file_get_contents` is called, PHP resolves relative paths to absolute paths and stores the results in the Cache. If the same path is encountered again, I read the result from memory and save myself the costly trip to the file system. This mechanism noticeably reduces system calls, especially when Composer autoloading loads many classes and configuration files. Important: The Realpath cache exists on a per-process basis, so each PHP-FPM worker only begins to benefit after a few requests, once it has built up its own cache. This results in a steady performance boost that pays off particularly well under high load.

Why Caching Matters in Large Frameworks

Large frameworks and numerous plugins generate countless requests per request File Accesses, which would have to resolve paths anew every time without caching. If the realpath cache is too small, it overwrites older entries, new ones are added, and I observe pure Thrashing. This results in repeated stat and lookup operations that consume time and place a load on I/O. In practice, this can reduce the number of system calls per request by about 5–15 %, which adds up significantly at high request rates. The more modular the application, the greater the leverage provided by a properly sized Realpath cache.

Here's how I determine the appropriate cache size

First, I count the unique paths in a typical request, estimate the average path length, and add about 128 bytes per entry Overhead. Based on the number, path length, and overhead, I calculate a `realpath_cache_size` that is sufficient Buffer offers. Many larger projects end up between 4 and 16 MiB, and very large monorepos can exceed that. It's important that the cache doesn't stay at its limit; otherwise, I lose the benefit because I have to keep clearing it out. I increase the size in increments, monitor the usage, and adjust accordingly.

Recommended Settings and Sample Values

Default values date back to a time when codebases were small and often no longer fit today's Setups. For many production applications, I set `realpath_cache_size` to 4096K to 16384K and extend `realpath_cache_ttl` to 360–600 Seconds or more. The key factors are project size, deployment frequency, and file system characteristics. The following table provides useful guidelines for reference and helps you get started with tuning. I then adjust the numbers based on monitoring and load testing.

Setting Frequent Default Good starting values Expected effect
realpath_cache_size 4096K (4 MiB) 4096K–16384K Reduced Thrashing when there are many files
realpath_cache_ttl 120–600 s 360–900 s Longer Cache Holds, fewer subsequent dissolutions

Examples in php.ini: realpath_cache_size = 4096K and for large frameworks realpath_cache_size = 16384K. When it comes to lifespan, I often use realpath_cache_ttl = 360 or higher for infrequent releases. This way, paths remain in memory across many requests without having to be constantly revalidated.

Choose TTL Wisely—Depending on the Deployment

The correct TTL depends heavily on the deployment process and the use of Symlinks ... When I switch between releases using symlink rotation, the cache must not serve outdated paths, so I set the TTL to a short value or trigger an FPM restart after the Rollout. In Kubernetes environments that use Secrets or ConfigMaps as volumes, I significantly reduce the TTL or temporarily disable the Realpath cache. More static web hosting scenarios, on the other hand, benefit from longer TTLs because paths rarely change. This is how I balance timeliness and speed to suit the environment.

Monitor and Verify

I regularly check using realpath_cache_get(), which paths in the Cache are, and with realpath_cache_size(), how much of that storage is being used. If usage is close to the configured size, I increase the Capacity Step by step. If the cache fills up extremely quickly, I take that as a sign that more memory is needed or that the TTL is too short. I check again after major plugin installations or framework updates. Only by knowing the numbers can you make informed tuning decisions.

Measurable Impact: How I Measure System Calls and Latencies

To ensure the tuning is robust, I take measurements before and after making changes. On Linux, I track file system calls per request using strace or perfect, either on a single FPM worker or via the CLI.

  • Single Request (CLI): strace -c -o /tmp/strace.txt php public/index.php provides an overview of how many stat(), openat() and lstat() incur.
  • Attach FPM Worker: strace -fp -e trace=file -o /tmp/strace-fpm.log Shows only file-related calls. Previously with P.S. Determine the worker's PID.
  • Load test: With tools such as from or hey I simulate a load and compare P95/P99 latencies for different cache sizes.

At the same time, I have PHP display the cache usage, for example in a debug endpoint or via the CLI:

<?php
$entries = realpath_cache_get();
$size    = realpath_cache_size();
printf("Entries: %d, Used: %d bytes (%.2f MiB)\n", count($entries), $size, $size/1048576);

This is how I can tell if the increase in the realpath_cache_size actually reduces misses and file system calls, rather than just tying up RAM. Ideally, the hit rate increases while P95 latencies drop noticeably.

Here's how I warm up the Realpath cache in a targeted way

Since the cache is created for each worker, it's worth Warm-up after deployment or a restart. The goal is for the most frequently used includes to be cached early on, before actual user traffic arrives.

  • Request Replays: I automatically test a handful of typical URLs (frontend, admin, API) after the rollout.
  • CLI Priming: A short bootstrap script loads key paths (autoloader, kernel, configuration, routes).
<?php
// warmup.php
require __DIR__.'/vendor/autoload.php';  // Composer
require __DIR__.'/config/bootstrap.php'; // Project-specific
require __DIR__.'/public/index.php';     // Front controller (may trigger a short run)
echo sprintf("Primed %d entries, used %d bytes\n",
    count(realpath_cache_get()), realpath_cache_size());

While this warm-up doesn't cover every real-world request, it maps out the most frequently used paths and noticeably shortens the initial cold-start phase for each worker.

Interaction with OPcache and the file system cache

OPcache speeds up the execution of PHP files, while Realpath shortens the path to the file, so I combine the two Techniques. For OPcache settings, I use tried-and-true values and refer to well-researched OPcache Optimization, so that bytecode and paths work together seamlessly. In addition, Realpath benefits from a warm OS cache that quickly provides directory lookups and metadata. This way, I avoid duplicate wait times during loading and parsing. Coordinating both levels yields noticeable improvements in response times.

Getting the Most Out of Composer Autoloading

The Composer autoloader is a key driver for path resolution. The more deterministic it is, the easier it is for the Realpath cache.

  • Optimize Classmap: composer dump-autoload -o Reduces directory scans and repeated lookups.
  • Strictly adhere to autoloadWith classmap-authoritative (Project settings) I avoid unnecessary fallbacks that would otherwise trigger additional path resolutions.
  • Organize the structure: Flat, consistent folder hierarchies and few special cases (e.g., includes that span modules or clients) help stabilize the cache load.

The result: fewer distinct paths per request, more reuse in the Realpath cache, and thus lower I/O costs.

FPM and Memory Budget: What's Realistic Per Worker

Because the Realpath cache exists per process, the configured size is multiplied by the number of FPM workers. I therefore plan to set aside a budget:

  • Example: 12 workers × 8 MiB = 96 MiB Realpath header; add to that the OPcache, PHP heap, and extension overhead.
  • Balancing: If OPcache has plenty of headroom, Realpath can get a few MiB more—or vice versa.
  • Pool-specific: Different FPM pools (Front, Admin, API) may have different realpath sizes, depending on their respective code footprints.

As the code grows with each release, the realpath_cache_size-typically takes this into account. That's why I regularly check peak utilization under load, not just when the system is idle.

Special Cases: Symlinks, Containers, and NFS

For symlink rollouts, I'll write a short TTL or restart FPM after deployment so that all workers load the latest paths. In containers with mutable volumes, I make sure the cache doesn't contain outdated Goals by adjusting the TTL. For NFS, it's also recommended to use a clean OPcache strategy and minimize directory changes as much as possible. If paths change at runtime, I explicitly clear the cache with clearstatcache(true) including the Realpath portion. Clear deployment rules prevent inconsistent states across workers.

Common Obstacles and Limitations

Because the cache exists per process, each one must Worker First, collect the paths before the effect takes hold. In setups with strict `open_basedir` restrictions, the Realpath cache has limited functionality, so I take this into account Boundaries during the planning phase. Caches that are too small lead to thrashing, while caches that are too large waste RAM—I use measurements to find the optimal point. I also keep in mind that Realpath is not a metadata or content cache, but rather stores only path resolutions. If you have the wrong expectations, you’ll overlook causes elsewhere.

Operational Safety: Common Fault Patterns and Quick Checks

Some symptoms clearly indicate Realpath issues—and can be quickly verified:

  • Fluctuating Latences After Deployment: Either the TTL is too long during symlink rotation or there was no warm-up. Solution: Set a short TTL, restart FPM, and then perform priming.
  • Many repeated stat()-ViewsWith strace visible; often the cache size is too small, or certain dynamic paths displace hot entries.
  • High variance among workers: Different caches per process. Solution: consistent warm-up and even request distribution.

A quick health check using PHP is often all it takes:

<?php
$entries = realpath_cache_get();
$byDir = 0; $byFile = 0;
foreach ($entries as $e) { $e['is_dir'] ? $byDir++ : $byFile++; }
printf("Dirs: %d, Files: %d, Used: %.2f MiB\n", $byDir, $byFile, realpath_cache_size()/1048576);

This way, I can see whether directories (many modules, vendor structures) or files (numerous configurations/classes) dominate the cache—and adjust the structure or size accordingly.

Stat Cache vs. Realpath Cache: Making a Conscious Distinction

In addition to the Realpath cache, PHP also maintains a Stat-Cache for results from stat() and related calls. Both caches can be accessed using clearstatcache() influence:

  • clearstatcache() Clears the stat cache (optional for a specific file).
  • clearstatcache(true) also clears the Realpath cache.

In rare cases—such as with long-running CLI workers that use dynamic mounts or during hot swaps—I use a targeted clearstatcache(true)-Hook after known changes. Otherwise, I let the TTL do its job and avoid unnecessary invalidations.

Practical Checklist for Hosting Environments

First, I determine the project size—that is, how many files a typical request loads—and then check the load on the Caches. Next, I choose a `realpath_cache_size` that accommodates all frequently used paths plus a buffer, and set a TTL that aligns with deployment cycles. Afterward, I observe the effects using monitoring and logs and carefully adjust the values rather than increasing them drastically. It’s also worth taking a look at the OS cache, such as the Linux setting VFS Cache Pressure, because Realpath benefits from fast directory lookups. This way, I can implement improvements without overlooking any side effects.

Scope and Configuration Paths: Where to Set Which Value

Depending on the environment, I maintain the parameters in different locations:

  • Global: php.ini for system-wide defaults.
  • Pro-Pool: In FPM pools as of php_admin_value[realpath_cache_size] and php_admin_value[realpath_cache_ttl] Tailor the sizing specifically for front-end and API.
  • Pro Directory: In .user.ini (if allowed), useful in shared hosting environments.

Important: Changes to the php.ini and FPM pool configurations require a restart or reload so that workers start with the new values.

CLI, Queue Workers, and Cron Jobs: Same Rules, Different Runtimes

CLI scripts and queue workers also benefit from the Realpath cache—however, the Service life often different:

  • Short-lived CLI jobs: The cache is recreated with each call. In this case, warm-up and a long TTL are of little use; what's more important is ensuring the cache is large enough so that repeated includes within the job itself are cached.
  • Daemons/Workers: Long-running processes (Supervisor, Systemd) build up a stable cache. After a Code Reload (Deploy) should restart the process; otherwise, outdated paths might remain in the cache.

Estimating Project Sizes and Storage Effects

An application with 4,000 unique paths and a path length of 80 bytes, plus 128 bytes of overhead per entry, requires roughly 832 KB Memory in the Realpath cache; I plan to allocate 4 MiB or more as a buffer. If the codebase grows significantly due to plugins or modules, I'll scale linearly and re-evaluate the Hits versus misses. On shared hosting servers, I also keep an eye on inode limits, because too many small files can put a strain on the entire system; this overview helps me with that Inode Limits. It's better to build in a buffer than to constantly run at the limit. That way, I save on system calls without using up unnecessary RAM.

In a Nutshell: My Tuning Roadmap

First, I count the number of files per request, then I set an appropriate Cache size and one that fits your deployment practices TTL. I then use `realpath_cache_get()` and `size()` to monitor the load, make incremental adjustments, and combine this with fine-tuning OPcache and the OS cache. During symlink rollouts, I keep the TTL short or restart FPM; in static environments, I use long lifetimes. The goal remains a high cache hit rate without wasting RAM. This is how I leverage the Realpath Cache as an underrated performance booster for consistent PHP performance.

Current articles