Sansec logo

Magento deployments without downtime

Sansec

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:

  1. Building on the server, so PHP compiles while customers wait.
  2. Stopping workers that still have open requests.
  3. New code that starts before the database matches it.
  4. 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.

Need expert advice? We are here to help!

Stay up to date with the latest eCommerce attacks

Sansec logo

experts in eCommerce security

Terms & Conditions
Privacy & Cookie Policy