Magento best practices

Performance and stability best practices for Magento 2 / Adobe Commerce on TurboStack - Redis, Varnish, Elasticsearch, OPcache, cron and production mode.

Magento 2 (Adobe Commerce) is resource-intensive, placing high demands on memory, CPU, and I/O. A fast, stable storefront depends on the right caching layers, a working cron, and production mode. TurboStack provisions most of this for you; this page covers what is already configured and what to optimize on top.

What TurboStack configures for you

Set the app type to magento2 and the platform prepares the host for a Magento store:

  • Document root - the vhost serves from the Magento pub/ directory inside your app root (/var/www/<user>/public_html), with a Magento-specific Nginx configuration (50main.conf).
  • Magento-aware Varnish - when Varnish is enabled, the platform installs a Magento-aware VCL on the host (/etc/varnish/conf.d/50_main.vcl), so the full-page cache understands the storefront.
  • Log rotation - everything under the app's var/log/ is rotated weekly via logrotate so disks do not fill up.
  • Database tuning - a Magento-specific MySQL setting (restrict_fk_on_non_standard_key = OFF) is applied for compatibility, and OPcache is enabled in the PHP runtime.

Enable what the store needs alongside it: MySQL, Redis, a search engine (Magento requires Elasticsearch or OpenSearch), and Varnish on the application.

Combine Magento's vendor guidance with the TurboStack options below. Most are one toggle away on the host's Services tab.

  • Run in production mode - set the deploy mode to production for live stores: bin/magento deploy:mode:set production. This compiles dependency injection (DI) and pre-generates static assets. After deploying code or configuration changes, run the standard sequence setup:upgrade -> setup:di:compile -> setup:static-content:deploy -> cache:flush (use setup:upgrade --keep-generated on production to preserve already-compiled code).
  • Enable Varnish full-page cache - this reduces time-to-first-byte significantly for storefront pages; keep it on for production.
  • Use Redis for sessions and cache - do not use file sessions on a busy store. See Redis cache tuning for the env.php block and its lifetime settings.
  • Keep Elasticsearch/OpenSearch healthy - it is required; size it for your catalog (see below).
  • Enable OPcache (and consider the realpath cache) in the PHP runtime to cut PHP parsing overhead.
  • Optimize assets and images - minify/merge JS and CSS, use WebP/optimized images, and serve pub/static and pub/media via a Content Delivery Network (CDN) when possible.
  • Use an HTTP cache / CDN in front - offload static and media delivery. If a CDN fronts the site, TurboStack can disable Varnish caching of static/media to avoid double caching.
  • Keep cron running - reindexing, price rules, emails, and message-queue consumers all depend on cron; never disable it on a live store.
  • Run message-queue consumers as services - on a busy store, run consumers as persistent systemd --user services rather than relying only on cron, so they restart on failure. See Message-queue consumers below.

Redis cache tuning

Magento reads its Redis connections from app/etc/env.php. TurboStack runs the two Redis instances; the store has to be pointed at them.

Below is a complete app/etc/env.php for a Magento store on TurboStack. The Redis part is the block between the //START and //END markers; everything around it is an ordinary Magento file, shown so you can see where the block belongs.

<?php
return [
    'backend' => [
        'frontName' => 'admin'
    ],
    'crypt' => [
        'key' => '<your-encryption-key>'
    ],

    'db' => [
        'table_prefix' => 'md_',
        'connection' => [
            'default' => [
                'host' => 'localhost',
                'dbname' => '<dbname>',
                'username' => '<dbuser>',
                'password' => '<dbpassword>',
                'active' => '1'
            ]
        ]
    ],
    'resource' => [
        'default_setup' => [
            'connection' => 'default'
        ]
    ],
    'x-frame-options' => 'SAMEORIGIN',
    'MAGE_MODE' => 'production',
//START
    'session' => [
        'save' => 'redis',
        'redis' => [
            'host' => '/var/run/redis-persistent/redis.sock',
            'port' => '0',
            'password' => '',
            'timeout' => '3',
            'persistent_identifier' => '',
            'database' => '1',
            'compression_threshold' => '2048', // Set 0 to disable
            'log_level' => '2',
            'max_concurrency' => '50',
            'break_after_frontend' => '5',
            'break_after_adminhtml' => '30',
            'first_lifetime' => '600',
            'bot_first_lifetime' => '60',
            'bot_lifetime' => '7200',
            'disable_locking' => '0',
            'min_lifetime' => '60',
            'max_lifetime' => '2592000'
         ]
    ],
    'cache' =>[
        'frontend' => [
           'default' => [
                'id_prefix' => '104_',
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'backend_options' => [
                    'server' => '/var/run/redis/redis.sock',
                    'port' => '0',
                    'persistent' => 'db2',
                    'database' => '2',
                    'password' => '',
                    'force_standalone' => '0',
                    'connect_retries' => '2',
                    'read_timeout' => '3',
                    'automatic_cleaning_factor' => '0',
                    'compress_threshold' => '2048',
                    // 'compress_data' => '1', // Let Magento pick best value, default 1
                    // 'compress_tags' => '1', // Let Magento pick best value, default 1
                    // 'compression_lib' => 'gzip', // Let Magento pick best value , prefer lz4 or snappy
                    'preload_keys' => [
                        '104_EAV_ENTITY_TYPES',
                        '104_GLOBAL_PLUGIN_LIST',
                        '104_DB_IS_UP_TO_DATE',
                        '104_SYSTEM_DEFAULT',
                    ],
                ]
            ],
            'page_cache' => [
                'id_prefix' => '104_',
                'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
                'backend_options' => [
                    'server' => '/var/run/redis/redis.sock',
                    'port' => '0',
                    'persistent' => 'db3',
                    'database' => '3',
                    'password' => '',
                    'force_standalone' => '0',
                    'connect_retries' => '2',
                    'read_timeout' => '3',
                    'compress_threshold' => '2048',
                    // 'compress_data' => '1', // Let Magento pick best value, default 1
                    // 'compress_tags' => '1', // Let Magento pick best value, default 1
                    // 'compression_lib' => 'gzip', // Let Magento pick best value , prefer lz4 or snappy
                ]
            ]
        ]
    ],
//END
    'cache_types' => [
        'config' => 1,
        'layout' => 1,
        'block_html' => 1,
        'collections' => 1,
        'reflection' => 1,
        'db_ddl' => 1,
        'eav' => 1,
        'customer_notification' => 1,
        'config_integration' => 1,
        'config_integration_api' => 1,
        'full_page' => 1,
        'config_webservice' => 1,
        'translate' => 1,
        'compiled_config' => 1
    ],
    'install' => [
        'date' => 'Thu, 27 Sep 2018 18:41:40 +0000'
    ]
];
  • Sessions on the persistent instance, caches on the cache instance. session points at /var/run/redis-persistent/redis.sock, both caches at /var/run/redis/redis.sock. See Sessions and cache for why they are split.

  • port is 0 because the connection goes over the socket, not over a network port. The socket path is in host for sessions and in server for the caches.

  • Each one gets its own database: 1 for sessions on the persistent instance, 2 for the default cache and 3 for the full-page cache. Clearing one then does not touch the others.

  • id_prefix must match the preload_keys entries. Both are 104_ here. Use your own prefix in both places, so keys stay unique when several sites share the instance.

  • The compression options are commented out on purpose. Magento picks its own values; lz4 or snappy are preferred over gzip if you do set compression_lib.

After changing env.php, flush the cache:

php bin/magento cache:flush

If caching was not enabled before, turn it on so Redis is actually used:

php bin/magento cache:enable

Varnish settings

Magento 2 has built-in support for Varnish. Run these as your system user from ~/public_html, to point it at the Varnish TurboStack runs:

bin/magento setup:config:set -n --http-cache-hosts=127.0.0.1:6081
magerun2 config:set system/full_page_cache/caching_application 2
magerun2 config:set system/full_page_cache/ttl 86400
magerun2 config:set system/full_page_cache/varnish/access_list 127.0.0.1,localhost
magerun2 config:set system/full_page_cache/varnish/backend_host 127.0.0.1
magerun2 config:set system/full_page_cache/varnish/backend_port 8080
  • Varnish listens on port 6081, and the web server behind it on 8080.
  • caching_application 2 selects Varnish instead of Magento's built-in full-page cache.
  • ttl is the default Time to Live (TTL) in seconds.

Switch Varnish on per application with varnish_enabled, under Configure application > Technologies. There is no host service to enable first. See Configure Varnish.

Message-queue consumers

Magento offloads asynchronous work - reindexing, transactional emails, and order or coupon processing - to message queues handled by consumer processes. On a busy store, run each consumer as a persistent systemd --user service so it restarts on failure, instead of relying on cron alone.

Create a reusable template unit at ~/.config/systemd/user/magento-consumer@.service:

[Unit]
Description=Magento Consumer (%i)
After=network-online.target
Requires=dbus.socket
StartLimitIntervalSec=0

[Service]
Type=simple
WorkingDirectory=%h/public_html
ExecStart=php %h/public_html/bin/magento queue:consumers:start %I --single-thread --max-messages=10000
RestartSec=10s
Restart=always

[Install]
WantedBy=default.target
  • %i is the consumer name, so one template serves every queue, and %h is your home directory.
  • --single-thread runs one thread per process; --max-messages=10000 restarts the consumer periodically to keep memory in check.

List the available consumers with php bin/magento queue:consumers:list, then enable and start one service per consumer (the name after @ is the consumer):

systemctl --user enable --now magento-consumer@sales.rule.update.coupon.usage.service
systemctl --user status magento-consumer@product_action_attribute.update.service

To drain queues faster on a busy store, run several consumers in parallel (more instances, or more of the same consumer). Keep the total number of consumers within the host's processor budget: never more than the core count, and keep a margin. Too many will slow the whole store. See Scale throughput with more instances.

For the systemd --user basics - including loginctl enable-linger so the services keep running after you log out - see How to manage user system services.

Sizing and scaling

TurboStack auto-tunes resource limits from the host's size, so start with the defaults. Override the tuning variables only when you have measured evidence (slow queries, cache evictions, search latency):

Variable Tune when
mysql_innodb_size The InnoDB buffer pool is too small for your catalog/order volume
redis_memory Redis is evicting keys under load
elasticsearch_heap_size Search is slow or the catalog is very large
varnish_cache_size The page cache hit ratio is low because the cache is too small

See Performance tuning before changing any of these. When a single host can no longer keep up, scale up the host before splitting services.

Stability

  • Back up regularly - verify scheduled Backups cover both the database and the pub/media, app/etc, and var directories.
  • Watch Health - monitor CPU, memory, and disk. Magento's var/ and media directories grow over time.
  • Keep versions current - track Magento/Adobe Commerce security patches and the PHP, MySQL, and Elasticsearch versions you run.
  • Test on a staging clone - always trial upgrades, extensions, and theme changes on a copy before publishing to production.