Magento deployments without downtime
by Sansec
Published in Guides
Deploy downtime has four causes. Each has one fix, and none of them is maintenance mode.
Downtime during a Magento deploy comes from four places:
- Building on the server, so PHP compiles while customers wait.
- Stopping workers that still have open requests.
- New code that starts before the database matches it.
- Cache flushes that make every node go cold at the same moment.
One fix per cause. Below are the steps for a Kubernetes rollout and for a plain server. If you read nothing else, read the next two paragraphs.
The two settings that do most of the work
Set the blue/green flag in app/etc/env.php (Magento 2.4.4 and later, open source):
'deployment' => ['blue_green' => ['enabled' => true]],
And branch your migration on the database status:
bin/magento setup:db:status
# exit 0 -> bin/magento app:config:import
# any other exit -> bin/magento setup:upgrade --keep-generated
setup:upgrade bumps module versions and flushes the config cache. Without the flag, every old node re-validates at that moment and throws "Please upgrade your database". The flag turns off that validator and the config change detector. That is all it does. It does not make a destructive schema change safe.
Keep schema changes additive
Order is the whole game. Schema ahead of code is safe when the change is additive. Code ahead of schema is fatal.
So: new columns and new tables only. No drops, no narrowing of a column, no new NOT NULL or UNIQUE constraint on a live table. If you need one of those, split it over two releases.
Kubernetes
Build in CI, not on the server
Run setup:di:compile and setup:static-content:deploy in the pipeline. Ship the result as an image. A deploy then only swaps code, and nothing compiles at runtime.
Roll, do not restart
Set maxUnavailable: 0 and maxSurge: 1. A new pod must pass its readiness probe before an old pod stops. Give old pods a termination grace period of 30 seconds and a preStop sleep of 10 seconds, so the load balancer stops sending traffic before PHP-FPM exits.
Migrate before the new code starts
Run one migration job before the rollout, using the setup:db:status branch above. The job finishes first, then the pods roll.
Two deploy paths
For a code-only release, skip the migration job and touch no cache.
For a schema or config change, run the migration job, roll out, then bin/magento cache:clean config full_page.
Clean named cache types. cache:flush empties the whole backend and every pod pays for the cold cache at the same moment. Never flush in the middle of a rollout.
Plain server
Build in CI
Compile and deploy static content in the pipeline. Ship a tarball. The server only unpacks it.
Unpack beside the live code
Use releases/<date>/ for code, shared/ for media, logs and env.php, and a current symlink. The site keeps serving the old release while you unpack.
Migrate first, from the new release
Run the setup:db:status branch from the new release directory, before you switch the symlink. Old code on a new additive schema is fine. New code on an old schema is not.
Switch atomically
ln -s releases/2026-09-09 current.new && mv -Tf current.new current
systemctl reload php-fpm
Reload, never restart. Nginx must use $realpath_root in its root and fastcgi_param settings, otherwise it keeps serving the old path from its resolved-path cache.
Cache
Code only: touch nothing. Schema or config change: bin/magento cache:clean config full_page after the switch. No cache:flush during a deploy.
Rollback
Point the symlink at the previous release directory and reload PHP-FPM again.
No maintenance mode
Maintenance mode is downtime by design. With the steps above in place, you do not need it.
Thanks to Francois Raubenheimer (Silvertree Brands) for sharing his deploy setup.
In this article
Protect your store now!
Block all known Magento attacks, while you schedule the latest critical patch until a convenient moment. No more downtime and instability from rushed patching.
Get Sansec Shield