What works without an outage, and what doesn't
Zero downtime is promised often and defined rarely. This article explains honestly where a symlink switch truly works without any outage, and where database migrations unavoidably require a short maintenance window.
Table of Contents
- 1. What zero downtime really means
- 2. What truly works without downtime
- 3. What doesn't work without downtime
- 4. setup:upgrade: the critical step
- 5. Expand and contract: DB migrations without downtime
- 6. Maintenance mode: when it's genuinely necessary
- 7. Pipeline strategy by release type
- 8. Monitoring after deploy: what to watch
- 9. Deployment types compared
- 10. Summary
- 11. FAQ
1. What zero downtime really means
Zero downtime does not mean that not a single process is restarted or that no single request ever takes longer than usual. It means the shop stays continuously reachable for end users and shows no visible error states. The symlink switch in a Magento deployment is atomic: after the switch, the web server points to a different directory without a single request ever receiving a 503. That is genuine zero downtime at the file level.
What zero downtime does not solve is the gap between the moment the new code becomes active and the moment the cache, the search index and the queue consumers have fully caught up to the new state. During this phase, users can see inconsistent states: stale cache content, products that haven't been indexed yet, queue messages still processed by the old code. This period of inconsistency is shorter than a maintenance window, but it is real. Anyone who promises zero downtime without addressing this period is delivering half the truth.
Genuine zero downtime for Magento is achievable, but it requires clear decisions per release type. Pure code releases without database migrations can be deployed entirely without downtime. Releases with database migrations require either the expand and contract strategy or a short, well defined maintenance window. This chapter explains which releases require which strategy.
2. What truly works without downtime
What can be deployed without downtime: pure code changes without database migrations, CSS and JavaScript updates, template changes, new PHP logic that doesn't touch the existing database schema, and configuration changes managed through Magento's configuration structure. For all of these release types, the symlink switch is sufficient. The old code and the new code both exist on the file system at the same time; the switch determines which code the web server uses for new requests.
What matters for genuine zero downtime: the new code must be compatible with the existing database schema. If a new module requires a new database column but setup:upgrade hasn't run yet, the new code will fail immediately after the symlink switch. That is no longer a zero downtime deployment. The compatibility check between new code and the current database schema must be part of the build or pre-deploy stage, not a hope after the switch.
3. What doesn't work without downtime
There are deployment scenarios where downtime is unavoidable, at least without elaborate expand and contract strategies. The first scenario is destructive database changes: renaming columns, dropping columns, changing column types. These operations cannot be made backward compatible, because the old code, which may still be accessing the database during the deployment, depends on the renamed or removed column.
The second scenario is complex data transformations: populating new columns from existing data, splitting tables, merging tables. With large data volumes, these operations can take anywhere from minutes to hours. During that time, the database sits in a half finished state that is compatible with neither the old nor the new code. The third scenario is Magento core updates with their own schema upgrade, where setup:upgrade cannot be skipped and takes significant time.
4. setup:upgrade: the critical step
bin/magento setup:upgrade is the step that most commonly causes downtime in Magento deployments. It runs all pending database migrations, updates the schema and modifies the configuration tables. For a Magento core update or a large module update, this step can take five to fifteen minutes. While setup:upgrade is running, the database schema and the code are incompatible, whether that's new code with an old schema or old code with a new schema.
The only safe strategy for a setup:upgrade with real downtime risk is maintenance mode. It prevents requests from reaching the application during the migration. Maintenance mode must be enabled before setup:upgrade and disabled immediately after it finishes. Running setup:upgrade without maintenance mode for schema changing migrations is not a controlled process, it's a gamble.
# Deploy job with controlled maintenance window for DB migrations
deploy:production:with-migration:
stage: deploy
variables:
# Set HAS_DB_MIGRATION=1 for releases with schema changes
HAS_DB_MIGRATION: "0"
script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- RELEASE_ID=$(date +%Y%m%d-%H%M%S)
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -s << SSH
set -euo pipefail
RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
mkdir -p "\$RELEASE_PATH"
SSH
- rsync -az --delete ./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/releases/$RELEASE_ID/"
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash -s << 'SSH'
set -euo pipefail
RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
# Link shared resources
ln -sfn "$DEPLOY_PATH/shared/app/etc/env.php" "$RELEASE_PATH/app/etc/env.php"
ln -sfn "$DEPLOY_PATH/shared/pub/media" "$RELEASE_PATH/pub/media"
# Enable maintenance mode ONLY for releases with DB migrations
if [ "$HAS_DB_MIGRATION" = "1" ]; then
cd "$DEPLOY_PATH/current"
bin/magento maintenance:enable
echo "Maintenance mode enabled, running DB migration"
# Switch symlink to new release
ln -sfn "$RELEASE_PATH" "$DEPLOY_PATH/current"
cd "$DEPLOY_PATH/current"
bin/magento setup:upgrade --keep-generated
bin/magento maintenance:disable
echo "Migration complete, maintenance mode disabled"
else
# Zero-downtime path: no maintenance needed
ln -sfn "$RELEASE_PATH" "$DEPLOY_PATH/current"
cd "$DEPLOY_PATH/current"
echo "No DB migration, zero-downtime deploy complete"
fi
bin/magento cache:flush
SSH
when: manual
only:
- tags
5. Expand and contract: DB migrations without downtime
The expand and contract strategy is the way to carry out database migrations without maintenance mode. The principle: every database change is split into two separate releases. The first release (expand) extends the schema in a backward compatible way, adding new columns while keeping the old ones. The code of both versions, old and new, is compatible with the extended schema. The second release (contract) removes the old columns or the no longer needed changes, once no code accesses the old columns anymore.
This strategy takes more effort than a simple setup:upgrade with maintenance mode. It requires developers to explicitly classify database migrations as "expand" or "contract" and plan accordingly. For Magento core updates it usually doesn't apply, because the core migrations aren't under your own control. For your own modules and your own schema changes, it is the preferred path when zero downtime is a non negotiable requirement.
6. Maintenance mode: when it's genuinely necessary
Maintenance mode in Magento is not a sign of poor deployment design. It's an explicit safety net for situations where the application is temporarily in an inconsistent state. It should be used sparingly, purposefully and with a clearly defined time window. A 30 second maintenance mode for a controlled database step is acceptable. A 30 minute maintenance mode for a disorganized build process is not.
The pipeline should treat maintenance mode as an explicitly set variable: HAS_DB_MIGRATION=1 enables it, HAS_DB_MIGRATION=0 (the default) leaves it off. This decision must be made deliberately before every deployment, documented, and communicated to the team. A maintenance mode that gets enabled automatically and indiscriminately on every deployment is a sign that the deployment process is not understood, not a sign that it's safe.
7. Pipeline strategy by release type
The GitLab pipeline should offer different deploy jobs for different release types. A deploy:production:no-migration job for pure code releases that manages entirely without maintenance mode. A deploy:production:with-migration job for releases with database migrations that enables maintenance mode and runs setup:upgrade in a controlled way. Both jobs exist in parallel in the pipeline; the team chooses before the deploy which path matches the current release.
This separation forces a conscious decision about the deployment type. It prevents a release with database migrations from accidentally being deployed through the zero downtime path and putting the application into an inconsistent state. It also prevents a simple code release from unnecessarily enabling maintenance mode and locking users out. The pipeline is where this decision gets structurally enforced.
8. Monitoring after deploy: what to watch
After every deployment, whether with or without maintenance mode, monitoring needs heightened attention for at least 30 minutes. The most important metrics right after a deploy are: HTTP error rate (5xx), database query times, PHP OPcache hit rate (drops after deploy, climbs back up afterward), queue consumer status and search index status. Anomalies in these metrics during the first 30 minutes after deploy point to problems that may require a quick rollback.
A verify job in the pipeline covers the immediate check. Monitoring covers the ongoing observation in the minutes after the verify job. Together, both give the team the confidence to declare a deployment complete and stable. Without monitoring after the deploy, there's no guarantee the new release behaves correctly in production, only a hope.
9. Deployment types compared
The table below shows the three most important deployment types in Magento projects with their respective characteristics. It makes clear that zero downtime is not a binary concept, but depends on the nature of the changes.
| Release type | Maintenance mode | setup:upgrade | Typical downtime |
|---|---|---|---|
| Pure code release | Not needed | Not needed | 0 seconds (genuine zero downtime) |
| New module (additive DB) | Optional (expand first) | Recommended | 0-30 seconds with expand strategy |
| Destructive DB change | Necessary | Necessary | 30 sec to 15 min (depending on data volume) |
| Magento core update | Recommended | Necessary | 5-15 min (depending on Magento version) |
| CSS/JS/template update | Not needed | Not needed | 0 seconds (genuine zero downtime) |
The table makes it clear: genuine zero downtime is fully achievable in Magento for the most common release types, pure code releases and template updates. For releases with database migrations, a decision is required that depends on the type of migration. This decision must be made deliberately for each release and structurally enforced in the pipeline.
10. Summary
Understanding zero downtime in Magento realistically means distinguishing between release types and choosing the right deployment path for each one. Pure code releases, template updates and CSS/JS changes can be deployed entirely without downtime, the symlink switch is the only step visible to users. Releases with database migrations require either the expand and contract strategy for genuine zero downtime, or a short, clearly defined maintenance window with maintenance mode.
The GitLab pipeline implements this distinction structurally: different deploy jobs for different release types, explicit variables for maintenance mode and DB migrations, and verify jobs that check the state of the application after the deploy. Zero downtime isn't a marketing promise, it's the result of concrete, realistically planned deployment decisions.
Zero Downtime in Magento, the Essentials at a Glance
What works without downtime
Pure code releases, template updates, CSS/JS changes without database migrations, the symlink switch is enough.
What requires downtime
Destructive database migrations, Magento core updates with schema upgrades, complex data transformations.
Expand and contract
For additive DB migrations: the expand release adds new columns, the contract release removes old ones. No maintenance needed.
Pipeline strategy
Two deploy jobs: without maintenance (default), with maintenance (for DB migrations). HAS_DB_MIGRATION as the controlling variable.
11. FAQ: Zero Downtime in Magento, Realistically
1Can Magento be deployed with fully zero downtime?
2setup:upgrade on every deployment?
3What is the expand and contract strategy?
4Maximum duration of maintenance mode?
5Why is maintenance sometimes better than zero downtime?
6How do I spot pending DB migrations?
7New code activated before setup:upgrade?
8How fast is the symlink switch?
9Does --keep-generated help with downtime?
10How to communicate planned maintenance windows?
Key takeaway: zero downtime isn't a binary state
Most Magento shops benefit from consistently deploying frequent, low risk changes (code changes, new features) without downtime, and scheduling a short, communicated maintenance window for rare, critical deploys (core updates, destructive migrations). That is what realistic zero downtime operation looks like.
Anyone chasing zero downtime as an absolute goal for every single deploy invests disproportionately in infrastructure. The pragmatic middle ground, differentiated by change type, is the most sustainable approach for productive Magento teams.