Configure and verify PHP OPcache for production
Measure script inventory, size OPcache deliberately, choose a deployment invalidation strategy, enable observability, and prove cache health.
Reduce PHP compilation overhead without serving stale code or exhausting the shared-memory cache.
- PHP 8.2, 8.3, 8.4, 8.5
- Supported host and recovery access Use a supported distribution, keep an independent root-capable session, and record the current package repositories before changing the PHP runtime.
cat /etc/os-release && uname -m - Application compatibility Confirm the application, framework, extensions, and deployment tooling explicitly support the target PHP branch.
- Configuration backup Archive the active PHP, web-server, and application configuration before packages, pools, or handlers change.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A measured OPcache configuration for the active PHP-FPM branch, with explicit bytecode memory, interned-string memory, script-key capacity, source-validation policy and safe production defaults.
- A deployment contract that states whether mutable files use timestamp checks or immutable releases restart FPM, plus a protected FPM-SAPI status endpoint for utilization and restart evidence.
- A canary, warm-up, freshness and rollback sequence that proves the web SAPI—not merely CLI—loads the intended settings and serves the current release.
- OPcache is installed from the approved repository, loaded by the exact FPM binary, large enough for the measured application with headroom, and not enabled for CLI without a separate reason.
- Repeated application requests increase FPM cache hits without OOM/hash restarts, excessive wasted space or key exhaustion; a deployment freshness test returns the current release.
- Operators can distinguish normal cold misses from undersizing, stale-code policy failure and invalid FPM configuration, and can restore the prior INI without deleting application data.
Architecture
How the parts fit together
Each FPM master owns a shared-memory OPcache used by its workers; CLI invocations are separate processes and do not report the FPM cache. PHP compiles source into bytecode, stores it in a bounded shared segment and indexes scripts up to `max_accelerated_files`. Interned strings use a dedicated portion. With timestamp validation enabled, FPM checks file modification metadata at the revalidation interval; with it disabled, the deployment must restart the selected `{{fpmService}}` after an atomic release switch. A localhost/authenticated endpoint calls `opcache_get_status(false)` inside the application FPM SAPI and returns only aggregate counters.
- A worker receives a PHP script, resolves its cache key and uses cached bytecode on a hit.
- A miss compiles and stores the script if memory/key capacity and update-protection rules allow it.
- Deployment either relies on timestamp validation or restarts FPM after an atomic release switch.
- Protected status and canary requests prove capacity, hits, restarts and release freshness.
Assumptions
- The exact PHP CLI/FPM binaries, INI scan paths and systemd service are known, and configuration plus application release rollback are available.
- `{{appRoot}}` contains the deployed PHP source to measure; representative warm traffic and a safe canary endpoint exist.
- The application supports PHP `{{phpVersion}}`, deployment semantics are documented, and no configuration manager will overwrite the INI during the change.
- `{{opcacheStatusPath}}` is reachable only from localhost or an authenticated management network and returns aggregate values without script lists.
Key concepts
- shared memory
- A bounded memory segment shared by workers of one PHP process manager; it is not shared with arbitrary CLI processes.
- interned strings
- Deduplicated strings stored in a separate OPcache allocation to reduce repeated memory use.
- accelerated file key
- An index entry for one cached script; key exhaustion can occur even when byte memory remains.
- timestamp validation
- Periodic metadata checks used to notice source changes without a process restart.
- wasted memory
- Invalidated cache space that is not immediately reusable and can eventually trigger a restart.
- warm-up
- Controlled requests that compile expected routes before measuring steady cache behavior or opening traffic.
Before you copy
Values used in this guide
{{appRoot}}Absolute deployed application root used for script inventory and freshness checks.
Example: /srv/billing/current{{phpVersion}}Installed PHP branch used in package, binary and INI paths.
Example: 8.3{{fpmService}}Exact active systemd unit for the intended FPM master.
Example: php8.3-fpm{{opcacheMemoryMiB}}Measured OPcache bytecode-memory budget in MiB.
Example: 256{{internedStringsMiB}}Measured interned-string budget in MiB.
Example: 32{{maxCachedFiles}}Prime-ish supported cache-key capacity above application script count.
Example: 20000{{opcacheStatusPath}}Local-only path returning aggregate FPM OPcache counters.
Example: internal/opcache-status{{domain}}Canary hostname used for warm-up and freshness tests.
Example: billing.example.comSecurity and production boundaries
- Do not expose `opcache_get_status(true)`, `phpinfo()` or script paths publicly; they reveal filesystem and runtime internals.
- The cache can preserve vulnerable or stale code until validation/restart policy takes effect. Deployment invalidation is a security control, not only performance tuning.
- Install the matching packaged extension; unmanaged binaries can load into every worker and expand the supply-chain risk.
Stop before continuing if
- Stop if the active FPM binary/service/INI path or application script inventory is unknown, current errors exist, or rollback cannot restore the prior INI.
- Do not disable timestamp validation until atomic release switch plus exact FPM restart is automated and tested.
- Do not accept the change when FPM status is unavailable, OOM/hash restarts occur, cache remains nearly full, or the freshness canary serves the previous release.
instruction
Inventory PHP files and current cache state
Count application scripts, total source size, current INI paths, and any existing OPcache directives.
Why this step matters
Script count/source bytes and active FPM policy are the minimum evidence for memory/key sizing and safe rollback.
What to understand
Count only deployed PHP files on the same filesystem view workers use and record symlinked release layout.
Capture CLI and FPM INI paths separately; the existing status endpoint or FPM dump is authoritative for web traffic.
System changes
- No persistent change; reads application PHP file sizes/counts and current PHP/OPcache configuration.
Syntax explained
find ... -printf '%s\n'- Emits each PHP file size for aggregation.
awk- Counts scripts and sums source bytes without reading contents.
php -i | grep- Shows current CLI directives as preliminary evidence.
Values stay on this page and are never sent or saved.
find {{appRoot}} -type f -name '*.php' -printf '%s\n' | awk '{n++; b+=$1} END {printf "files=%d bytes=%d\n",n,b}'; php --ini; php -i | grep -E '^opcache\.'Script count, bytes, and active directives are recorded.
Checkpoint: Checkpoint: inventory
Continue whenScript count, source bytes, release layout, current directives, FPM INI paths and baseline counters are recorded.
Stop whenApplication root is wrong, duplicate releases are unexpectedly scanned, FPM state cannot be queried, or baseline errors/restarts exist.
If this step fails
Inventory returns zero scripts.
Likely causeThe root is wrong, files are in a symlinked release/container, or permissions hide them.
readlink -f /srv/billing/currentfind /srv/billing/current -maxdepth 3 -type f -name '*.php' | head
ResolutionLocate the exact deployed worker-visible root before sizing.
Security notes
- Do not export source names or full status script lists; aggregate counts are sufficient.
Alternatives
- Generate the same inventory during the immutable build and compare it with the deployed release manifest.
Stop conditions
- Application root is wrong, duplicate releases are unexpectedly scanned, FPM state cannot be queried, or baseline errors/restarts exist.
command
Install the distribution OPcache package
Use the package matching the active PHP branch instead of compiling an unmanaged extension; stop on an unsupported package manager rather than falling through after a failed transaction.
Why this step matters
The extension must match the active PHP ABI and repository; a failed package-manager path must not silently fall through to another manager.
What to understand
Ubuntu/Debian use the explicit versioned package, while Enterprise Linux uses the selected stream's `php-opcache`.
Preview package origin/transaction and stop if it replaces the intended branch or removes unrelated extensions.
System changes
- Installs the matching packaged OPcache extension and its INI integration.
Syntax explained
command -v apt-get/dnf- Selects one package manager before the transaction.
php{{phpVersion}}-opcache- Pins the Debian-family package to the active branch.
php-opcache- Uses the active Enterprise Linux PHP stream.
Values stay on this page and are never sent or saved.
if command -v apt-get >/dev/null 2>&1; then sudo apt-get install --yes php{{phpVersion}}-opcache; elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y php-opcache; else printf 'Unsupported package manager\n' >&2; exit 1; fiThe matching Zend OPcache extension package is installed from the approved distribution or PHP repository.
Checkpoint: Checkpoint: install
Continue whenThe package comes from the approved repository, matches PHP ABI and does not change unrelated runtime packages.
Stop whenPackage origin/version is wrong, a transaction removes unrelated packages, or the manager is unsupported.
If this step fails
PHP reports `undefined symbol` after install.
Likely causeExtension ABI and runtime branch differ or duplicate files load.
php -vphp --inirpm -q php-opcache 2>/dev/null || dpkg -l 'php*-opcache'
ResolutionRestore the matching packaged branch and remove only the unintended duplicate; do not force-load the binary.
Security notes
- A Zend extension executes in every worker; use only signed approved packages.
Alternatives
- Keep OPcache disabled until the correct repository build is available.
Stop conditions
- Package origin/version is wrong, a transaction removes unrelated packages, or the manager is unsupported.
config
Size shared memory and interned strings
Start from measured application size with headroom, then set memory, interned strings, and key capacity explicitly and tune them from observed FPM utilization.
Why this step matters
Explicit memory and key limits prevent hidden defaults from becoming capacity policy and make later saturation measurable.
What to understand
Choose bytecode and interned-string MiB from observed warm cache plus release-overlap headroom.
`max_accelerated_files` must exceed script count and use one of PHP's supported internal sizes; verify actual capacity from status.
System changes
- Creates/replaces the reviewed OPcache INI for PHP `{{phpVersion}}` with the displayed settings.
Syntax explained
memory_consumption- MiB for cached bytecode and related data.
interned_strings_buffer- MiB for deduplicated interned strings.
max_accelerated_files- Maximum number of script keys retained.
max_wasted_percentage- Wasted-memory threshold that may schedule a cache restart.
/etc/php/{{phpVersion}}/mods-available/opcache.iniValues stay on this page and are never sent or saved.
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption={{opcacheMemoryMiB}}
opcache.interned_strings_buffer={{internedStringsMiB}}
opcache.max_accelerated_files={{maxCachedFiles}}
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.file_update_protection=2
opcache.max_wasted_percentage=10Values stay on this page and are never sent or saved.
sudoedit /etc/php/{{phpVersion}}/mods-available/opcache.iniOPcache memory and key capacity exceed the measured application footprint.
Checkpoint: Checkpoint: memory
Continue whenEvery placeholder is replaced, values exceed measured need with planned overlap, and file ownership/mode prevent worker edits.
Stop whenValues are copied without measurement, host memory budget is insufficient, or multiple INIs define conflicting directives.
If this step fails
FPM cannot allocate the configured shared memory.
Likely causeBudget is too large for host/container limits or another mapping constraint.
free -msystemctl show php8.3-fpm -p MemoryMaxjournalctl -u php8.3-fpm -n 100
ResolutionRestore the prior INI, then choose a measured value within cgroup/host limits.
Security notes
- Root owns runtime policy; application/deployer must not change INI through a writable package/config path.
Alternatives
- Place a separate root-owned override in the FPM scan directory instead of editing the packaged module file.
Stop conditions
- Values are copied without measurement, host memory budget is insufficient, or multiple INIs define conflicting directives.
config
Choose timestamp validation or immutable deployments
For mutable releases enable validate_timestamps with a short revalidate frequency; disable it only when deployment always resets FPM.
Why this step matters
Source validation must match deployment behavior; otherwise a successful release can continue executing old bytecode.
What to understand
Mutable deployments keep validation enabled and use a small frequency; two seconds balances prompt change detection and metadata calls.
Immutable deployments may disable it only when every FPM master restarts after the atomic symlink/container release switch.
System changes
- No separate file; verifies the validation contract encoded in the OPcache INI and deployment procedure.
Syntax explained
validate_timestamps=1- Allows FPM to detect file modification changes.
revalidate_freq=2- Checks eligible cached script timestamps at most every two seconds.
Values stay on this page and are never sent or saved.
sudo grep -E '^opcache\.(validate_timestamps|revalidate_freq)' /etc/php/{{phpVersion}}/mods-available/opcache.iniThe source-change policy is explicit and matches deployment behavior.
Checkpoint: Checkpoint: validation policy
Continue whenOne documented mutable or immutable policy exists and the deployment pipeline implements its required validation/restart.
Stop whenTimestamp validation is disabled without automatic exact-service restart and release-marker verification.
If this step fails
Changed source appears only after manual restart.
Likely causeValidation is disabled/overridden or frequency/deployment path differs.
sudo php-fpm8.3 -tt | grep -E 'validate_timestamps|revalidate_freq'grep -R -n '^opcache.validate_timestamps' /etc/php/8.3
ResolutionRestore timestamp validation or fix and test immutable restart automation.
Security notes
- Stale cache can preserve code after an emergency security deployment.
Alternatives
- Use timestamp validation as the safe default when release immutability is not absolute.
Stop conditions
- Timestamp validation is disabled without automatic exact-service restart and release-marker verification.
config
Keep production-safe directives
Enable OPcache for web requests, keep CLI disabled unless measured, retain file-update protection, and avoid force_restart_timeout that masks deployment problems.
Why this step matters
Web caching should be enabled without accidentally creating persistent CLI caches or weakening file-update protection during non-atomic writes.
What to understand
Keep CLI disabled unless a long-running CLI workload was measured; ordinary commands gain little from a per-process cache.
File update protection delays caching files changed in the last two seconds, reducing partial deployment risk but not replacing atomic releases.
System changes
- No separate mutation; verifies safety directives in the authoritative OPcache INI.
Syntax explained
enable=1- Enables OPcache for supported non-CLI SAPIs.
enable_cli=0- Avoids short-lived CLI shared-memory behavior.
file_update_protection=2- Avoids caching files modified within two seconds.
Values stay on this page and are never sent or saved.
sudo grep -E '^opcache\.(enable|enable_cli|file_update_protection|max_wasted_percentage)' /etc/php/{{phpVersion}}/mods-available/opcache.iniWeb cache is enabled and safety thresholds are explicit.
Checkpoint: Checkpoint: safety
Continue whenWeb cache is enabled, CLI policy is deliberate, update protection and waste threshold are explicit.
Stop whenA deployment writes files in place without atomicity, CLI is enabled without a use case, or directives conflict across scan files.
If this step fails
New files intermittently miss or old files persist during deploy.
Likely causeIn-place writes race protection/revalidation or multiple release paths are active.
readlink -f /srv/billing/currentfind /srv/billing/current -type f -mmin -2 | head
ResolutionAdopt atomic versioned releases and one explicit invalidation policy.
Security notes
- Never weaken update protection to compensate for an unsafe in-place deployment.
Alternatives
- Use immutable release directories and restart the exact service after pointer switch.
Stop conditions
- A deployment writes files in place without atomicity, CLI is enabled without a use case, or directives conflict across scan files.
verification
Validate PHP configuration
Check every loaded INI and ensure OPcache is visible to the exact FPM binary, using the versioned Debian/Ubuntu name or the unversioned Enterprise Linux name.
Why this step matters
CLI module presence and FPM parser success are separate gates; both must use the intended PHP branch before reload.
What to understand
Resolve the versioned or unversioned FPM binary explicitly and run its full configuration test as root.
Inspect loaded INI paths for duplicate Zend extension declarations and startup warnings.
System changes
- No runtime change; validates the exact PHP and FPM configuration.
Syntax explained
command -v php-fpm...- Selects the installed branch-specific FPM binary.
php -m- Confirms CLI can load Zend OPcache without startup failure.
php-fpm -tt- Tests and dumps FPM configuration without serving traffic.
Values stay on this page and are never sent or saved.
FPM_BIN="$(command -v php-fpm{{phpVersion}} || command -v php-fpm)"; php --ini; php -m | grep -i '^Zend OPcache$'; sudo "$FPM_BIN" -ttZend OPcache is loaded and the exact FPM configuration test succeeds.
Checkpoint: Checkpoint: syntax
Continue whenOne OPcache extension loads, intended INI files scan, and FPM test exits successfully without warnings.
Stop whenBinary/branch is wrong, extension loads twice, startup warning appears, or parser fails.
If this step fails
PHP says OPcache is already loaded.
Likely causeTwo INI files declare `zend_extension`.
php --inigrep -R -n 'zend_extension.*opcache' /etc/php
ResolutionKeep one authoritative packaged/override declaration and retest both SAPIs.
Security notes
- Do not suppress startup errors; they can indicate ABI or supply-chain problems.
Alternatives
- Validate a staged copy with the exact FPM binary before installing the final override.
Stop conditions
- Binary/branch is wrong, extension loads twice, startup warning appears, or parser fails.
command
Reload FPM during a controlled deployment
Reload the explicitly identified FPM service so new INI values take effect; use a restart if immutable deployments disable timestamp validation.
Why this step matters
FPM workers must map the new shared-memory policy; reloading the wrong unit produces a false success.
What to understand
Use the explicitly inventoried `{{fpmService}}`, record pre/post service timestamps and watch error logs.
For immutable deployments with validation disabled, use the documented restart semantics rather than assuming reload clears every cache.
System changes
- Reloads `{{fpmService}}`, replacing worker/cache state as supported by the service configuration.
Syntax explained
systemctl reload- Requests the unit's configured graceful reload action.
systemctl is-active- Confirms the selected unit remains active.
Values stay on this page and are never sent or saved.
sudo systemctl reload {{fpmService}}; systemctl is-active {{fpmService}}The selected FPM service stays active.
Checkpoint: Checkpoint: reload
Continue whenThe exact FPM unit remains active, new workers load the intended settings, canary health succeeds and logs are clean.
Stop whenService lacks safe reload semantics, workers fail, health regresses, or previous INI/release cannot be restored.
If this step fails
Service is active but requests fail after reload.
Likely causeNew workers cannot allocate/load config or the handler points to a lost socket.
systemctl status php8.3-fpm --no-pagerjournalctl -u php8.3-fpm -n 150 --no-pagerss -lxnp | grep php
ResolutionRestore prior INI, validate and reload/restart the same unit, then prove health before traffic.
Security notes
- Keep an independent session and do not reload every PHP service indiscriminately.
Alternatives
- Canary a separate pool/service with the new INI before moving production traffic.
Stop conditions
- Service lacks safe reload semantics, workers fail, health regresses, or previous INI/release cannot be restored.
verification
Inspect cache utilization from the FPM SAPI
Query an application-owned status endpoint restricted to localhost or an authenticated management network; a CLI cache is a different process and is not accepted as FPM evidence.
Why this step matters
Only an FPM-executed status call reports the shared cache serving the application; CLI statistics can be false or unrelated.
What to understand
Return aggregate memory, key, hit/miss and restart values with `opcache_get_status(false)`, never the script list.
Bind the route to loopback or strong management authentication and test that the public domain cannot reach it.
System changes
- No configuration change in this step; queries the already created protected FPM status endpoint.
Syntax explained
curl --fail- Fails on HTTP errors rather than printing a misleading body.
127.0.0.1- Keeps the example request local to the host.
opcache_get_status(false)- Returns aggregate status without cached-script paths.
Values stay on this page and are never sent or saved.
curl --fail --silent --show-error http://127.0.0.1/{{opcacheStatusPath}}{"used_memory":67108864,"free_memory":201326592,"num_cached_scripts":1842,"hits":285120,"misses":1842,"oom_restarts":0}Checkpoint: Checkpoint: observe
Continue whenFPM aggregate counters are returned, public access is denied, warm hits increase and restart counters remain zero.
Stop whenEndpoint is public, returns script paths/secrets, reports another FPM, or OOM/hash restarts rise.
If this step fails
Local endpoint returns false or empty counters.
Likely causeOPcache is disabled in that FPM, no script has executed, or request reaches another handler.
curl -v http://127.0.0.1/internal/opcache-statussudo php-fpm8.3 -tt | grep opcache
ResolutionTrace the exact handler/SAPI and warm one canary route; do not substitute CLI evidence.
Security notes
- Aggregate cache metrics are still internal operational data; restrict and remove temporary endpoints after integrating monitoring.
Alternatives
- Use a local exporter executing inside the target FPM pool with the same no-script-list policy.
Stop conditions
- Endpoint is public, returns script paths/secrets, reports another FPM, or OOM/hash restarts rise.
Finish line
Verification checklist
for i in 1 2 3; do curl --fail --silent --output /dev/null https://{{domain}}/; doneRequests succeed and OPcache hit count increases.touch {{appRoot}}/public/index.php && curl --fail --silent https://{{domain}}/healthThe timestamp policy exposes the current release, or the deployment restart procedure does so.Recovery guidance
Common problems and safe checks
The local status endpoint reports OPcache disabled while CLI lists the module.
Likely causeFPM scans another INI directory, the wrong pool/socket serves the endpoint, or `opcache.enable=0` is overridden for FPM.
php --inisudo php-fpm8.3 -ttsudo grep -R -n '^opcache.enable' /etc/php/8.3
ResolutionMap the endpoint to the exact FPM master and correct one authoritative INI; CLI presence is not accepted as FPM proof.
OOM or hash restarts increase after warm-up.
Likely causeByte memory, interned strings or key capacity is below the measured release footprint, or multiple releases remain addressable.
curl -sS http://127.0.0.1/internal/opcache-statusfind /srv/billing/current -type f -name '*.php' | wc -l
ResolutionRemove unintended duplicate release paths, then resize the specific exhausted resource with headroom and controlled reload.
A deployment still serves old code.
Likely causeTimestamp checks are disabled/slow, immutable deployment omitted the restart, or traffic reaches another FPM/host.
php -i | grep -E '^opcache.(validate_timestamps|revalidate_freq)'systemctl show php8.3-fpm -p ActiveEnterTimestampcurl -v https://billing.example.com/health
ResolutionRestore validation or restart every intended FPM master after the atomic switch; prove a release marker at all ingress nodes.
Reference
Frequently asked questions
Why does CLI `opcache_get_status()` return false?
This guide keeps `opcache.enable_cli=0`; even if enabled, CLI owns a different short-lived cache and does not prove FPM state.
Should memory equal the total PHP source size?
No. Compiled bytecode, interned strings, metadata, framework behavior and duplicate release paths differ; measure actual FPM utilization with headroom.
Is `validate_timestamps=0` always faster?
It removes checks but can serve stale vulnerable code indefinitely when restart automation fails. Use it only with a proven immutable deployment contract.
Recovery
Rollback
Restore the prior OPcache INI and reload FPM; disable the extension only if application stability requires it.
- Restore the archived opcache.ini and validate the FPM configuration.
- Reload FPM and verify application health and error logs.
- If disabling OPcache, remove only its enabled symlink or package after confirming performance impact.
Evidence