← Back to blog

Magento 2 Production Mode: Switching and Deploying

Rodrigo Biazzi

Magento 2 has three application modes, and running a live store in anything other than production mode costs speed and exposes details that should stay private. Switching modes is one command. Deploying correctly in production mode, so the store does not break for a few minutes on every release, takes a bit more planning. This article covers what production mode changes, how to switch, and a deploy sequence for Magento Open Source and Adobe Commerce 2.4.8 that works on a single server and scales to a proper build pipeline.

The three modes

  • default: what a fresh install uses. Static files are generated on demand, errors are logged but not shown. It is a middle ground that is not meant for development or for production.
  • developer: for local work. Generated code and static files are created on the fly, errors are shown in the browser, and caches are easier to work with. Slow by design.
  • production: for live stores. Static files must be deployed beforehand, generated code must be compiled, errors are only logged, and the application never tries to write to pub/static at runtime.

Check the current mode with:

bin/magento deploy:mode:show

The mode is stored in app/etc/env.php under MAGE_MODE. Setting it in the web server config through an environment variable is also possible, but keeping it in env.php means the CLI and the web server always agree.

What changes in production mode

Static content is served from pub/static only

In production mode Magento expects every theme and locale to have its static files in pub/static. Missing files are not generated. If you forget to deploy a locale, the store loads without CSS or JavaScript for that store view.

Generated code must exist

Interceptors, factories, proxies and the compiled DI configuration live in generated/. In production mode they must be built with setup:di:compile before the code runs. If a class is missing, you get a fatal error instead of on the fly generation. The generated code guide explains what ends up in that folder.

Errors are not displayed

Exceptions go to var/log and var/report. The visitor sees an error page with a report number. That is what you want on a live store.

Some admin settings are hidden

Developer settings under Stores, Configuration, Advanced, Developer are not shown in production mode. You set them with bin/magento config:set or in app/etc/config.php instead.

Switching to production mode

bin/magento deploy:mode:set production

This command enables maintenance mode, clears generated code and static files, runs DI compilation and static content deployment, and then disables maintenance mode. On a live store that means downtime for as long as compilation and static deploy take, which can be many minutes.

If you already compiled and deployed static files yourself, use --skip-compilation to switch the mode only:

bin/magento deploy:mode:set production --skip-compilation

In practice I only use deploy:mode:set once, when a project moves to production mode for the first time. After that, the mode stays in env.php and each deploy runs the individual commands in the right order.

A deploy sequence for one server

This is the minimum sequence for a release on a single server where you update code in place. It is not zero downtime, but it keeps the maintenance window short and predictable.

git pull
composer install --no-dev --optimize-autoloader

bin/magento maintenance:enable

bin/magento setup:upgrade --keep-generated
bin/magento setup:di:compile
bin/magento setup:static-content:deploy en_US pt_BR -j 4
bin/magento cache:flush

bin/magento maintenance:disable
sudo systemctl reload php8.3-fpm

Some notes:

  • --no-dev keeps development packages like PHPUnit out of production.
  • setup:upgrade on its own deletes generated code. --keep-generated keeps it, which only makes sense here because compilation runs right after.
  • List every locale used by any store view in the static content deploy. If the admin uses a different locale from the storefront, include it too.
  • The FPM reload clears OPcache when opcache.validate_timestamps is off.

The weakness of this sequence is that compilation and static deploy run while the store is in maintenance mode. That is the part a build pipeline removes.

Moving the build out of the deploy

Compilation and static content deploy do not need to run on the live server, and with some preparation they do not even need a database. The steps:

1. Dump config into config.php

Static content deploy needs to know which themes and locales are in use. Normally it reads that from the database. If you dump the relevant config into the versioned app/etc/config.php, a build server can read it from the file:

bin/magento app:config:dump scopes themes i18n

Commit the updated config.php. Values dumped this way are locked in the admin, which is the point. Theme and locale assignment become part of the code.

2. Build an artifact

On the build server or CI runner, starting from a clean checkout:

composer install --no-dev --optimize-autoloader
bin/magento setup:di:compile
composer dump-autoload --optimize --apcu
bin/magento setup:static-content:deploy en_US pt_BR -j 4 --area frontend --area adminhtml

The result is a folder with vendor, generated and pub/static ready to go. Package it as an archive.

3. Deploy the artifact

On the server, unpack the artifact into a new release folder, link shared folders such as pub/media, var and app/etc/env.php, then decide whether the database needs an upgrade:

cd /var/www/magento/releases/20261110120000
if ! bin/magento setup:db:status; then
    bin/magento maintenance:enable
    bin/magento setup:upgrade --keep-generated
fi
bin/magento app:config:import
ln -sfn /var/www/magento/releases/20261110120000 /var/www/magento/current
sudo systemctl reload php8.3-fpm
bin/magento cache:flush
bin/magento maintenance:disable

setup:db:status returns a non zero exit code when schema or data patches are pending. When nothing changed in the database, the release goes out without maintenance mode at all. The full approach is in the zero downtime deploy guide, and the CI/CD with GitHub Actions guide shows how to build the artifact in a pipeline.

Things that break in production mode

  • A store view with no static files. Someone added a locale in the admin but not to the deploy command.
  • Code that writes to pub/static at runtime. Some old extensions do this. It fails or produces files that the next deploy deletes.
  • Missing generated classes. A module added or a constructor changed without running compilation. The error mentions a class that does not exist, often ending in Interceptor or Factory.
  • Wrong file permissions. The web user must be able to write to var, generated, pub/static and pub/media during deploy, but in production mode nothing should need write access to code folders at runtime.
  • Cron running during deploy. A cron job that starts with half deployed code can fail or leave locks. Stop cron for the deploy window, or at least run the deploy when no heavy jobs are scheduled.

The sections of env.php and config.php touched here are explained in more detail in the env.php and config.php guide.

FAQ

How do I check which mode Magento 2 is running in?

Run bin/magento deploy:mode:show. The value is stored as MAGE_MODE in app/etc/env.php.

Does deploy:mode:set production cause downtime?

Yes, it enables maintenance mode while it compiles and deploys static content. Use --skip-compilation if you already ran those steps yourself.

Can I run setup:static-content:deploy without a database?

Yes, if you first dump scopes, themes and i18n into app/etc/config.php with app:config:dump. The build can then read themes and locales from the file.

Why does my store load without CSS after switching to production mode?

Static files for that theme or locale were not deployed. Add the missing locale to setup:static-content:deploy and run it again.

I am available for freelance Magento and Adobe Commerce work, including deploy pipelines. If you need help with this, get in touch.

Related articles

Magento 2 env.php vs config.php Explained

What goes in Magento 2 app/etc/env.php and config.php, how config precedence works, app:config:dump, --lock-env vs --lock-config and keeping secrets out of Git.