OPcache preloading, introduced in PHP 7.4 and refined in PHP 8.x, loads specified PHP files into shared memory when PHP-FPM starts. These files are then available to all requests without any file system access or compilation — they are simply there in memory. For framework-heavy applications like Laravel or Symfony, preloading can reduce request overhead by 15-30%.
How Preloading Works
Without preloading, every PHP request must:
- Check if the file is in OPcache (memory lookup)
- If not, read the file from disk
- Compile PHP to opcodes
- Store opcodes in OPcache
- Execute opcodes
With preloading, steps 1-4 are eliminated for preloaded files. The opcodes are loaded into memory when PHP-FPM starts and persist until restart.
Enabling Preloading
; php.ini
opcache.preload = /var/www/app/preload.php
opcache.preload_user = www-data
; Required OPcache settings
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.interned_strings_buffer = 32
Laravel Preload Script
<?php
// /var/www/app/preload.php
// Preload Laravel framework files that are used on every request
require_once __DIR__ . '/vendor/autoload.php';
$files = [
// Core Laravel files loaded on every request
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Container/Container.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Foundation/Application.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Http/Request.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Http/Response.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Routing/Router.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Routing/Route.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Support/Collection.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Support/Str.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Support/Arr.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/View/View.php',
__DIR__ . '/vendor/laravel/framework/src/Illuminate/Session/Store.php',
];
// Application models and frequent controllers
$appFiles = glob(__DIR__ . '/app/Models/*.php');
$files = array_merge($files, $appFiles);
$count = 0;
foreach ($files as $file) {
if (is_file($file)) {
try {
opcache_compile_file($file);
$count++;
} catch (Throwable $e) {
// Log but don't fail — some files may have unresolvable dependencies
error_log("Preload failed for {$file}: " . $e->getMessage());
}
}
}
error_log("Preloaded {$count} files");
Symfony Preload Script
<?php
// Symfony generates a preload file automatically
// After warmup, use the generated file:
// opcache.preload = /var/www/app/var/cache/prod/App_KernelProdContainer.preload.php
// To generate:
// php bin/console cache:warmup --env=prod
// Or create a custom one:
$files = require __DIR__ . '/var/cache/prod/preload_classes.php';
foreach ($files as $file) {
opcache_compile_file($file);
}
WordPress Preload Script
<?php
// /var/www/wordpress/preload.php
$files = [
__DIR__ . '/wp-includes/class-wp.php',
__DIR__ . '/wp-includes/class-wp-query.php',
__DIR__ . '/wp-includes/class-wpdb.php',
__DIR__ . '/wp-includes/formatting.php',
__DIR__ . '/wp-includes/functions.php',
__DIR__ . '/wp-includes/plugin.php',
__DIR__ . '/wp-includes/option.php',
__DIR__ . '/wp-includes/taxonomy.php',
__DIR__ . '/wp-includes/post.php',
__DIR__ . '/wp-includes/meta.php',
__DIR__ . '/wp-includes/cache.php',
__DIR__ . '/wp-includes/http.php',
__DIR__ . '/wp-includes/rest-api.php',
];
// Preload active theme functions
$theme = 'your-theme';
$themeFiles = glob(__DIR__ . "/wp-content/themes/{$theme}/*.php");
$files = array_merge($files, $themeFiles);
foreach ($files as $file) {
if (is_file($file)) {
opcache_compile_file($file);
}
}
Auto-Generating Preload Lists
<?php
// Use OPcache status to find most-used files
// Run this after the application has been serving traffic
$status = opcache_get_status(true);
$scripts = $status['scripts'];
// Sort by hits (most frequently accessed files)
usort($scripts, fn($a, $b) => $b['hits'] - $a['hits']);
// Generate preload file for top 200 files
$preloadFile = "<?php\n// Auto-generated preload list\n";
$count = 0;
foreach (array_slice($scripts, 0, 200) as $script) {
$path = $script['full_path'];
// Skip test files and dev dependencies
if (strpos($path, '/tests/') !== false) continue;
if (strpos($path, '/vendor/bin/') !== false) continue;
$preloadFile .= "opcache_compile_file('{$path}');\n";
$count++;
}
file_put_contents('/var/www/app/preload-generated.php', $preloadFile);
echo "Generated preload for {$count} files\n";
Monitoring Preloading
<?php
// Check preload status
$status = opcache_get_status();
echo "Preload enabled: " . ($status['preload_statistics']['enabled'] ? 'Yes' : 'No') . "\n";
echo "Preloaded scripts: " . $status['preload_statistics']['scripts'] . "\n";
echo "Preloaded memory: " . round($status['preload_statistics']['memory_consumption'] / 1024 / 1024, 2) . " MB\n";
echo "Preloaded functions: " . $status['preload_statistics']['functions'] . "\n";
echo "Preloaded classes: " . $status['preload_statistics']['classes'] . "\n";
// Full OPcache status
echo "\nOPcache memory used: " . round($status['memory_usage']['used_memory'] / 1024 / 1024, 2) . " MB\n";
echo "OPcache memory free: " . round($status['memory_usage']['free_memory'] / 1024 / 1024, 2) . " MB\n";
echo "Cache hit rate: " . round($status['opcache_statistics']['opcache_hit_rate'], 2) . "%\n";
Deployment Considerations
# Preloaded files persist in memory until PHP-FPM restarts
# After deployment, you MUST restart PHP-FPM to reload preloaded files
# Deployment script
#!/bin/bash
cd /var/www/app
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Graceful restart to reload preloaded files
sudo systemctl reload php8.3-fpm
# WARNING: opcache.validate_timestamps should be 0 in production
# This means OPcache won't detect file changes — you MUST restart FPM
Common Pitfalls
- Circular dependencies: Preloading file A that depends on file B which depends on A will fail. Order matters or use
opcache_compile_file()which handles this better thanrequire. - Memory usage: Preloading too many files wastes shared memory. Focus on files used by every request.
- Stale code: Forgetting to restart FPM after deployment means the old preloaded code is still served.
- Dev environment: Disable preloading in development — you want file changes to take effect immediately.
Summary
OPcache preloading is a free performance boost for PHP applications. By loading framework and frequently-used application files into shared memory at startup, you eliminate per-request file I/O and compilation overhead. Start with the framework core files, add your most-hit application files, and use OPcache statistics to refine the list over time. The typical improvement is 15-30% reduction in request processing time, with the biggest gains on I/O-constrained VPS instances.