
September 07, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
PHP OpCache configuration for production is the difference between a server that compiles every request and one that serves precompiled bytecode from shared memory. On real client projects running Laravel 12 or Laravel 13 on Ubuntu with PHP-FPM, I treat OpCache as mandatory infrastructure — not an optional speed tweak. This guide covers the ini settings I actually deploy, how they interact with PHP-FPM pool configuration, and the deploy-time gotchas that leave stale code running after a release.
opcache.enable=1, allocate enough opcache.memory_consumption, disable opcache.validate_timestamps in production, and reload PHP-FPM after each deploy.What Does PHP OpCache Do in a Production Stack?
PHP is an interpreted language. Without OpCache, the Zend engine tokenises, parses, and compiles every .php file on every request. That work is pure CPU overhead. OpCache stores the compiled opcode in shared memory so subsequent requests reuse it.
The performance gain is immediate. A typical Laravel application loads hundreds of files per request. OpCache can cut that compile phase to near zero. On a production Laravel application I maintain, OpCache plus sensible Redis caching is the baseline before touching CDN or database tuning.
OpCache is bundled with PHP 8.3, 8.4, and 8.5. You do not install it separately on modern builds. Confirm it is active before tuning anything else.
php -v
php -m | grep -i opcache
php -i | grep "opcache.enable" If opcache.enable shows Off, your production server is leaving performance on the table. Fix that before scaling hardware or rewriting queries.
Which OpCache Settings Should You Use in Production?
OpCache settings live in a dedicated ini fragment. On Ubuntu with PHP 8.5, the file is typically /etc/php/8.5/fpm/conf.d/10-opcache.ini. CLI and FPM use separate configs — always tune the FPM config for web traffic.
Below is a production baseline I use on Laravel and Symfony deployments. Adjust memory based on your codebase size.
; /etc/php/8.5/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.enable_cli=0
; Memory — increase if opcache_get_status shows near-full usage
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
; Production: do not re-stat files on every request
opcache.validate_timestamps=0
opcache.revalidate_freq=0
; JIT (PHP 8.x) — useful for CPU-heavy workloads
opcache.jit=1255
opcache.jit_buffer_size=128M
; Prevent stale includes in long-lived workers
opcache.max_wasted_percentage=10
; Preloading (optional, powerful for large apps)
; opcache.preload=/var/www/app/preload.php
; opcache.preload_user=www-data Memory sizing
opcache.memory_consumption sets shared memory in megabytes. A small WordPress site may need 64 MB. A Laravel 13 app with a large vendor/ tree often needs 192–512 MB. Check usage with opcache_get_status() or a one-off script.
<?php
$status = opcache_get_status(false);
echo "Used: " . round($status['memory_usage']['used_memory'] / 1048576, 1) . " MB\n";
echo "Free: " . round($status['memory_usage']['free_memory'] / 1048576, 1) . " MB\n";
echo "Cached scripts: " . $status['opcache_statistics']['num_cached_scripts'] . "\n"; If used memory stays above 90%, raise opcache.memory_consumption. Evicted scripts mean cache churn and lost performance.
File count limits
opcache.max_accelerated_files must exceed your total PHP file count. Count them on the server:
find /var/www/myapp -name "*.php" | wc -l Set the limit to at least 1.5× that number. Laravel projects with many packages can exceed 10,000 files easily. The default of 10,000 is too low for most modern frameworks.
validate_timestamps — the production trade-off
When opcache.validate_timestamps=1, PHP checks file modification times and recompiles changed files. That is convenient in development. In production it adds filesystem stat calls on every request.
Set opcache.validate_timestamps=0 in production. PHP will never auto-detect file changes. You must reload PHP-FPM after every deploy. That is the correct trade-off for stable production traffic.
| Setting | Development | Production | Why |
|---|---|---|---|
opcache.enable | 1 | 1 | Always on for realistic perf testing |
opcache.validate_timestamps | 1 | 0 | Auto-reload in dev; manual FPM reload in prod |
opcache.memory_consumption | 128 | 256–512 | Large vendor trees need headroom |
opcache.max_accelerated_files | 10000 | 20000+ | Framework + packages exceed defaults |
opcache.jit | off or tracing | 1255 | CPU-bound code benefits; I/O-bound less so |
opcache.preload | optional | recommended | Loads core classes once at FPM start |
Official reference for every directive lives in the PHP OpCache configuration documentation. Bookmark it when auditing a server someone else configured.
How Do You Invalidate OpCache After a Deployment?
This is where most production incidents start. You deploy new code. Users still hit old logic. OpCache cached the previous bytecode and validate_timestamps=0 prevents automatic refresh.
I've seen this on sister sites sharing a Deployer 7 pipeline. The symlink swaps correctly. PHP-FPM never reloads. The site runs yesterday's code until someone notices.
Reload PHP-FPM after deploy
Add this to your deployment script. With Deployer 7, a typical task looks like this:
task('php:fpm:reload', function () {
run('sudo systemctl reload php8.5-fpm');
});
after('deploy:symlink', 'php:fpm:reload'); Use reload, not restart, for zero-downtime on a correctly configured pool. A full restart drops active connections. Reload gracefully replaces workers.
On Apache with mod_php, restart Apache instead. Most production Laravel stacks use PHP-FPM behind Nginx or Apache as a reverse proxy. See LEMP stack setup for the full wiring.
Alternative invalidation methods
These exist but I prefer FPM reload because they are reliable and simple.
opcache_reset()— Clears the entire cache for that FPM pool. Requires a web-accessible script or artisan command. Risky if exposed publicly.opcache_invalidate($file, true)— Targets one file. Impractical for full deploys with thousands of changed files.- Temporarily enable validate_timestamps — Works but adds stat overhead. Not a production pattern.
For Laravel, an Artisan command that calls opcache_reset() behind authentication is a useful emergency tool. Do not rely on it as your primary deploy step.
Should You Enable OpCache Preloading for Laravel or Symfony?
Preloading is the most underused OpCache feature in PHP 8.x. At FPM startup, PHP executes a preload script that loads specified classes into OpCache permanently. Every worker inherits them without lazy compilation on first request.
The gain is measurable on cold requests. First-page loads after worker spawn drop noticeably. For high-traffic sites, that matters during deploys and traffic spikes.
Laravel preload.php example
Laravel 12 and Laravel 13 ship with a stub at preload.php in the project root. Enable it in your OpCache ini:
opcache.preload=/var/www/myapp/current/preload.php
opcache.preload_user=www-data The preload script typically loads framework core classes:
<?php
// preload.php
require __DIR__ . '/vendor/autoload.php';
$app = require __DIR__ . '/bootstrap/app.php';
$app->boot();
// Preload commonly used classes
$classes = [
Illuminate\Foundation\Application::class,
Illuminate\Http\Request::class,
Illuminate\Http\Response::class,
Illuminate\Routing\Router::class,
Illuminate\Database\Eloquent\Model::class,
];
foreach ($classes as $class) {
if (class_exists($class) || interface_exists($class)) {
opcache_compile_file((new ReflectionClass($class))->getFileName());
}
} Preloading requires care. A syntax error in the preload script prevents FPM from starting. Test on staging first. Your staging environment should mirror production OpCache settings — see staging environment setup for the checklist.
On a legal-tech portal I built with Laravel, preloading plus OpCache cut cold-start latency on document upload routes. The win was modest but consistent. Combined with speed optimization work on assets and queries, it added up.
How Do You Tune OpCache JIT for PHP 8.5 Workloads?
PHP 8.x introduced JIT compilation alongside OpCache. JIT translates hot opcode paths into native CPU instructions. It helps CPU-bound code. It does little for typical CRUD apps that wait on MySQL or Redis.
The recommended production setting is opcache.jit=1255 with a buffer of 64–128 MB. The four-digit mode controls when and how aggressively JIT compiles.
Measure before chasing JIT gains. Use load testing with k6 to compare latency with JIT on and off. If p95 response time does not move, your bottleneck is elsewhere.
Most eCommerce and CMS workloads — WooCommerce 11.1, Magento 2.4.x, custom Laravel carts — are I/O-bound. OpCache itself delivers the big win. JIT is icing.
PHP-FPM interaction
OpCache is shared across workers in a pool. JIT buffer is part of that shared segment. Combined with proper PHP-FPM pool tuning, you get consistent worker behaviour under load.
Key FPM settings that pair with OpCache:
pm.max_children— More workers share the same OpCache segment; memory is efficient.pm.max_requests— Recycling workers clears per-worker state but OpCache persists in shared memory.request_terminate_timeout— Long requests do not corrupt OpCache; they just hold a worker.
What OpCache Mistakes Break Production PHP Apps?
After years of debugging deploy and performance issues, these patterns repeat across client servers in Nepal and abroad.
Stale code after deploy
Symptom: fix is live on disk but behaviour unchanged. Cause: no FPM reload with validate_timestamps=0. Fix: add reload to CI/CD. Verify with a unique string in a response header or footer during deploy testing.
OpCache disabled on FPM but enabled on CLI
Running php artisan commands shows OpCache active. Web requests do not use it. Cause: separate ini files. Fix: edit /etc/php/8.5/fpm/conf.d/10-opcache.ini, not the CLI version.
Memory too small — silent evictions
Performance degrades gradually as the codebase grows. New vendor packages push cached scripts out. Monitor opcache_statistics.cache_full and oom_restarts in status output.
Preload path wrong after symlink deploy
If opcache.preload points to an absolute path inside a release directory, it breaks on the next deploy. Point it at the stable current symlink path instead.
Running multiple PHP versions
Servers with PHP 8.3 and 8.5 side-by-side need separate OpCache configs per version. A common mistake on shared hosting and VPS setups. Each FPM service has its own shared memory segment.
For ongoing monitoring, add OpCache metrics to your server checklist. A monthly review under support and maintenance catches memory creep before it becomes user-facing slowness.
Monitoring checklist
Run this after deploy and during performance audits:
- Confirm
opcache.enable=1in FPM phpinfo output. - Check memory usage stays below 85% of allocated.
- Verify
num_cached_scriptsmatches expected file count. - Confirm FPM reload ran in deploy logs.
- Watch for
oom_restartsincrementing — signals undersized memory.
On projects like Nepal Gift Card and Adventure Third Pole Trek, OpCache is step one in any performance review. Step two is database indexing. Step three is application caching. Skipping step one wastes effort on steps two and three.
Key Takeaways
- Enable OpCache in the FPM ini file — not just CLI — with at least 256 MB for Laravel-sized codebases.
- Set
opcache.validate_timestamps=0in production and reload PHP-FPM after every deploy. - Raise
opcache.max_accelerated_filesabove your actual PHP file count to prevent silent evictions. - Add preloading for large PHP 8.5 apps; test the preload script on staging before enabling in production.
- Pair OpCache with PHP-FPM reload in your Deployer or GitLab CI pipeline — never deploy code without invalidating cache.
- Monitor
opcache_get_status()monthly; memory pressure grows as packages accumulate.
People Also Ask
Is OpCache enabled by default in PHP 8.5?
OpCache is compiled into official PHP 8.5 builds but may ship disabled in distribution packages. Ubuntu and Debian often enable it in a conf.d fragment. Always verify with php -i | grep opcache.enable on the FPM SAPI, not just CLI.
Does OpCache work with Docker and container deployments?
Yes. Mount your OpCache ini as a config volume or bake it into the image. Reload the container's PHP-FPM process after code updates. In multi-stage Docker builds for Laravel, OpCache config belongs in the runtime stage alongside FPM pool settings.
How much memory should OpCache use for WordPress or WooCommerce?
WordPress 7.1 with WooCommerce 11.1 and a moderate plugin set typically needs 128–192 MB. Heavy plugin stacks or page builders may need 256 MB. Check cached script count and memory usage after importing a production database clone.
Can OpCache cause security issues?
OpCache itself is not a security vulnerability. The risk is operational: stale cached code after deploy can leave patched files inactive until FPM reload. Never expose opcache_reset() endpoints publicly. Treat FPM reload as a required deploy step, not optional.
Ship Faster PHP With Correct OpCache Settings
PHP OpCache configuration for production is not exotic tuning. It is baseline infrastructure for any PHP 8.3+ application serving real traffic. Set the ini values, disable timestamp validation, reload FPM on deploy, and monitor memory usage as your codebase grows.
If your Laravel or WordPress site still feels slow after OpCache is correct, the bottleneck has moved to queries, caching, or front-end assets. That is a different problem with different fixes. Start with OpCache — it takes thirty minutes and pays back on every request.
Need help auditing a production server or fixing deploy pipelines that skip FPM reload? Review the Linux system administration and testing and optimization services, or contact us for a server review. For related reading, see Ubuntu server setup for PHP apps, Dockerizing Laravel for production, and PostgreSQL vs MySQL for production. Use the JSON formatter when debugging API responses during performance work.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

