Zero Downtime in Magento: What Actually Works Without Outages and What Doesn't
AI generated
CI/CD
.yml
GitLab · Magento · Zero Downtime · Realism · Deployment Strategy
Zero Downtime in Magento, Realistically:
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.

13 min read Zero downtime · Symlink · setup:upgrade · DB migration · Maintenance mode Magento 2.4 · PHP 8.4 · GitLab CI/CD

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?
For pure code releases without DB migrations: yes. The symlink switch is atomic. With destructive database migrations, a short maintenance window is often unavoidable.
2setup:upgrade on every deployment?
No. Only for new database migrations. Pure code releases can skip setup:upgrade. --keep-generated speeds it up when generated code is already in the artifact.
3What is the expand and contract strategy?
Two step migration: expand adds new columns (additive). Contract removes old columns in a later release. No maintenance mode needed.
4Maximum duration of maintenance mode?
Under 5 minutes for a controlled DB migration. Longer windows suggest migrations that aren't optimized. Plan outside peak hours.
5Why is maintenance sometimes better than zero downtime?
With destructive DB migrations, a short maintenance window protects data integrity. Forcing zero downtime can lead to inconsistent states.
6How do I spot pending DB migrations?
bin/magento setup:db:status shows pending migrations. Run it as a pre-deploy job in the pipeline to automatically determine the deployment type.
7New code activated before setup:upgrade?
If the new code requires a new column that doesn't exist yet, it fails with database errors. The most common mistake in releases without maintenance mode.
8How fast is the symlink switch?
Milliseconds, a single kernel syscall. New requests after the switch hit the new release immediately. The fastest method for a code transition.
9Does --keep-generated help with downtime?
Yes. Skips DI compile in setup:upgrade because generated code is already in the artifact. Significantly reduces setup:upgrade runtime.
10How to communicate planned maintenance windows?
Magento shows a configurable maintenance page. Plan outside peak hours. Announce via newsletter or social media ahead of larger updates.

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.