← Back to blog

Magento 2 env.php vs config.php Explained

Rodrigo Biazzi

Two files in app/etc control how a Magento 2 installation boots: env.php and config.php. Both are PHP files that return arrays, both can contain store configuration, and both are edited by bin/magento commands. The difference between them decides what goes into Git, what stays on the server, and why some admin fields are greyed out.

This article explains what each file contains in Magento 2.4.8, how configuration precedence works, and the commands that manage them.

The short version

config.phpenv.php
PurposeShared application configurationEnvironment specific configuration
In GitYesNo
Typical contentModule list, scopes, themes, shared settingsDatabase, cache, sessions, queue, crypt key, mode
Same on all serversYesNo

If a value is the same on development, staging and production, it belongs in config.php. If it changes per environment or is a secret, it belongs in env.php.

config.php

The module list

The most important part of config.php is the modules array. Every module has an entry with 1 for enabled or 0 for disabled:

<?php
return [
    'modules' => [
        'Magento_Store' => 1,
        'Magento_Catalog' => 1,
        'Magento_TwoFactorAuth' => 1,
        'Vendor_Shipping' => 1,
    ],
];

module:enable and module:disable edit this array, and setup:upgrade adds newly installed modules to it. The order in the file reflects the module load order resolved from the sequence declarations in each module.xml. Commit this file whenever it changes. A module missing from config.php in production is simply not loaded, which is a common source of "it works on my machine" problems.

Scopes, themes and shared settings

app:config:dump can export more into config.php:

bin/magento app:config:dump scopes themes
bin/magento app:config:dump system

scopes writes websites, store groups and store views. themes writes the registered themes. With those in the file, setup:static-content:deploy and setup:di:compile can run in a build environment without a database, which is what makes a CI build possible. See the GitHub Actions article for a build that relies on this.

system writes store configuration values under a system key. Values that are marked as sensitive or environment specific are written to env.php instead. Be careful with a full dump on an existing store. Every exported value becomes locked in the admin, which can surprise the people who manage the store. Exporting only the specific paths you want to control from code is usually better, using config:set --lock-config.

When config.php changes on deploy, run app:config:import to import scopes and themes into the database. setup:upgrade also triggers the import. app:config:status tells you whether the files and the database are in sync.

env.php

env.php is written by setup:install and setup:config:set. A trimmed example from a production style setup:

<?php
return [
    'backend' => [
        'frontName' => 'admin_x7k2'
    ],
    'crypt' => [
        'key' => 'base64...'
    ],
    'db' => [
        'table_prefix' => '',
        'connection' => [
            'default' => [
                'host' => 'db.internal',
                'dbname' => 'magento',
                'username' => 'magento',
                'password' => 'secret',
                'model' => 'mysql4',
                'engine' => 'innodb',
                'initStatements' => 'SET NAMES utf8;',
                'active' => '1',
                'driver_options' => [
                    1014 => false
                ]
            ]
        ]
    ],
    'resource' => [
        'default_setup' => [
            'connection' => 'default'
        ]
    ],
    'MAGE_MODE' => 'production',
    'session' => [
        'save' => 'redis',
        'redis' => [
            'host' => 'redis.internal',
            'port' => '6379',
            'database' => '2'
        ]
    ],
    'cache' => [
        'frontend' => [
            'default' => [
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'backend_options' => [
                    'server' => 'redis.internal',
                    'port' => '6379',
                    'database' => '0'
                ]
            ],
            'page_cache' => [
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'backend_options' => [
                    'server' => 'redis.internal',
                    'port' => '6379',
                    'database' => '1'
                ]
            ]
        ]
    ],
    'cache_types' => [
        'config' => 1,
        'layout' => 1,
        'block_html' => 1,
        'full_page' => 1
    ],
    'lock' => [
        'provider' => 'db'
    ],
    'directories' => [
        'document_root_is_pub' => true
    ],
    'x-frame-options' => 'SAMEORIGIN',
    'http_cache_hosts' => [
        [
            'host' => 'varnish.internal',
            'port' => '6081'
        ]
    ]
];

The cache_types list is shortened here. A real file lists every cache type. Some keys worth knowing:

  • crypt/key: the encryption key for sensitive config values, payment credentials and tokens. Losing it means those values cannot be decrypted. Back it up with the same care as the database password.
  • backend/frontName: the admin URL path. A non default value reduces automated attacks on the admin login.
  • MAGE_MODE: developer, default or production, set with deploy:mode:set.
  • cache and session: where caches and sessions are stored. Redis settings are covered in Redis for cache and sessions.
  • cache_types: which caches are enabled. cache:enable and cache:disable edit this.
  • queue and cron_consumers_runner: AMQP connection and how cron starts queue consumers.
  • http_cache_hosts: Varnish hosts that receive purge requests.
  • lock: the lock provider, which matters when cron runs on more than one server.

Prefer setup:config:set over manual edits where an option exists. It validates input and writes the right structure. Manual edits are fine for keys without a command, but a syntax error in env.php takes the whole site down, so check with php -l app/etc/env.php before reloading.

Configuration precedence

Store configuration, the values you see under Stores, Configuration in the admin, can come from four places. From highest to lowest priority:

  1. Environment variables named like CONFIG__DEFAULT__WEB__SECURE__BASE_URL
  2. The system section of env.php
  3. The system section of config.php
  4. The core_config_data table in the database

A value set in any of the first three is shown as locked in the admin, with a note that it is set in a file or environment. Editing the database row has no effect while a file value exists, which is a classic debugging trap. If a configuration change does not take, check the files first.

The environment variable format is CONFIG__ followed by the scope, an optional scope code, and the path, all in uppercase with double underscores as separators. For a website scoped value: CONFIG__WEBSITES__BASE__PAYMENT__CHECKMO__ACTIVE. These variables must reach PHP-FPM and CLI processes, which usually means setting them in the PHP-FPM pool configuration and in the environment that runs cron.

Writing config values from the command line

bin/magento config:set web/secure/base_url https://shop.example.com/

bin/magento config:set --lock-env web/secure/base_url https://shop.example.com/

bin/magento config:set --lock-config catalog/frontend/grid_per_page 24

bin/magento config:sensitive:set payment/some_method/api_key
  • Without flags, the value goes to the database, as if saved in the admin.
  • --lock-env writes to env.php and locks the field. Use it for base URLs, hosts and anything that differs per environment.
  • --lock-config writes to config.php and locks the field. Use it for settings that should be the same everywhere and controlled through Git.
  • config:sensitive:set writes values that are declared as sensitive by their module into env.php. It only accepts paths that are registered as sensitive or environment specific.

Use --scope and --scope-code for website or store view values.

Practical rules

  • Commit config.php, never env.php. Keep an env.php.example or a provisioning script so a new environment can be created without guessing.
  • Store the production env.php in a secrets manager or encrypted backup, separate from the code and the database dump.
  • Lock in files only what really needs to be locked. Store managers lose the ability to change locked values in the admin.
  • On deploy, keep env.php in a shared folder linked into each release, as described in zero downtime deploys.
  • When something behaves differently between environments, compare the system sections of both files and the environment variables before looking at code.

FAQ

Should app/etc/env.php be committed to Git?

No. It holds database passwords, the encryption key and environment specific hosts. Keep it on each server and out of the repository. config.php, on the other hand, should be committed.

Why is a setting greyed out in the Magento admin?

It is set in env.php, config.php or an environment variable. Values from those sources override the database, so the admin shows them as locked. Change the file or the variable, not the admin field.

What happens if I lose the crypt key in env.php?

Every value encrypted with it, such as payment credentials, API keys and integration tokens, can no longer be decrypted. Back up env.php securely, separately from the code.

What is the difference between config:set --lock-env and --lock-config?

--lock-env writes the value to env.php, for settings that differ per environment. --lock-config writes it to config.php, for settings that should be the same everywhere and live in version control.

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

Related articles