PHP 7: Maintaining and Upgrading Legacy Apps · Operating Legacy PHP in Production

Zero-Downtime Deploys for PHP

Atomic release directories, OPcache resets and safe migrations.

The classic PHP deploy: upload code to a new releases/<timestamp> directory, install dependencies there, run backward-compatible migrations, atomically switch a current symlink, then reload PHP-FPM (resetting OPcache). Tools like Deployer automate this. Keep the previous releases for instant rollback, and ensure Nginx uses $realpath_root so it resolves the new symlink target.

#!/usr/bin/env bash
set -euo pipefail
REL=/var/www/app/releases/$(date +%Y%m%d%H%M%S)
git clone --depth 1 --branch "$TAG" [email protected]:acme/app.git "$REL"
cd "$REL" && composer install --no-dev --optimize-autoloader
ln -s /var/www/app/shared/.env "$REL/.env"
php bin/migrate.php                       # backward-compatible migrations only
ln -sfn "$REL" /var/www/app/current_tmp && mv -T /var/www/app/current_tmp /var/www/app/current
sudo systemctl reload php7.4-fpm          # clears OPcache
ls -dt /var/www/app/releases/* | tail -n +6 | xargs rm -rf   # keep 5 releases

Common pitfalls

  • Using $document_root in Nginx so PHP keeps resolving the old path.
  • Destructive migrations before new code is live.
  • Copying files over the live directory (users hit half-deployed code).

Key points

  • New directory per release + atomic symlink switch
  • Reload FPM to reset OPcache
  • Keep old releases for rollback

Further reading: Deployer documentation

Practice questions

Why use mv -T instead of ln -sfn directly on current?

Answer

mv -T renames atomically, so there's never a moment where current doesn't exist; ln -sfn unlinks then creates, leaving a tiny gap.

Why must migrations be backward compatible in this deploy flow?

Answer

Migrations run before the switch while old code still serves traffic, and rollback restores old code against the migrated database.

Open in the app to mark lessons complete, take notes and track your progress.