Skip to content
Muhammet Şafak
tr
Asked by: Şükrü Answered:

How do I stop OPcache from causing 500s during a zero-downtime DeployerPHP deploy?


Question

We deploy with DeployerPHP: `releases/X` is created, `composer install` runs, assets are built, and finally the `current` symlink points at the new folder. On that symlink flip, old code lingers in PHP-FPM's OPcache (cache poisoning) and we get 500s. How do I safely wire `php-fpm reload` and OPcache clearing into the pipeline?

Answer

Short answer: flipping the symlink is atomic but not enough — after the flip you must gracefully reload PHP-FPM, and never leave OPcache stuck on old code after a release.

What you’re hitting is classic OPcache cache poisoning: OPcache keys compiled bytecode by file path. Since the current symlink’s own path never changes, workers keep serving the previous release’s bytecode; when some workers see the old realpath and others the new one, paths mix and you get 500s.

  1. Cleanest path: gracefully reload PHP-FPM after the symlink. kill -USR2 <fpm-master> or systemctl reload php-fpm makes FPM pick up the new realpath and refresh OPcache. Reload, not restart: in-flight requests finish, new workers compile the new code, no downtime. In Deployer, hook this onto the deploy:symlink step.
  2. Alternative: call opcache_reset() from a deploy hook. Hitting the FPM socket with cachetool is cleaner than triggering a reset endpoint over the web — CLI and FPM keep separate OPcaches, so resetting via php artisan won’t touch FPM. A reload already covers this, so you rarely need it on top.
  3. Tuning opcache.revalidate_freq alone won’t fix it. It helps when the same file path changes; but across releases the path differs, and revalidate won’t save you. At a release boundary, a reset/reload is the guaranteed fix.
  4. Octane is a different story entirely. Under Octane, code is served from memory, not disk; long-lived workers hold old classes in RAM. After the symlink flip you must run php artisan octane:reload to refresh the workers — otherwise no amount of OPcache work helps, the old code stays up.

Bottom line: make the flow explicit — flip the symlink → gracefully reload PHP-FPM (or octane:reload if you’re on Octane) → optionally opcache_reset. Hook it onto the symlink step in Deployer and never leave OPcache pinned to old code after a release. The deploy-side cost of Octane holding code in memory is its own hub topic; know that persistent-process model well and you won’t skip this reload step.

Related Reading

Tags: #ci-cd#laravel#deploy
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind