# Magento best practices

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

When you deploy a `magento2` app, the platform sets up a complete, tuned Magento stack:

- **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`).
- **Redis caching** - sessions use a dedicated persistent Redis socket, while the default cache and full-page cache use a separate Redis instance. This is configured at install time, so checkout state stays fast and the database is offloaded.
- **Varnish full-page cache** - when Varnish is enabled, the platform installs a Magento-aware VCL. It configures Magento to use it (`http-cache-hosts`, `caching_application = 2`, backend on port 8080, Varnish on 6081, default Time to Live (TTL) 86400s).
- **Elasticsearch/OpenSearch** - configured as the catalog search engine with a per-site index prefix. Magento requires this; the storefront will not run without it.
- **Cron jobs** - three cron entries are installed for the system user: the main `bin/magento cron:run`, `setup:cron:run`, and the updater cron. These drive indexing, emails, and scheduled jobs.
- **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.

## Recommended optimizations

Combine Magento's vendor guidance with the TurboStack options below. Most are one toggle away on the host's [Services](../../platform/hosts/services.md) 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.
- **Keep Redis on for sessions and cache** - already configured; do not switch back to file sessions on a busy store. See [Redis cache tuning](#redis-cache-tuning) for the `env.php` cache 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](#message-queue-consumers) below.

## Redis cache tuning

TurboStack configures Magento's Redis backends at install time, so you do not set this up by hand. Sessions use the persistent Redis instance; the default cache and full-page cache use the cache instance. The reference below shows the cache block in `app/etc/env.php` and the values worth tuning (`Cm_Cache_Backend_Redis`, port 6379, no password):

```php
<?php
return [
    'cache' => [
        'frontend' => [
            'default' => [
                'backend' => 'Cm_Cache_Backend_Redis',
                'backend_options' => [
                    'server' => '127.0.0.1',
                    'port' => '6379',
                    'database' => '0',
                    'id_prefix' => 'magento_prod_',
                    'compress_data' => '1',
                    'default_lifetime' => '600',
                    'min_lifetime' => '60',
                    'max_lifetime' => '86400',
                ],
            ],
            'page_cache' => [
                'backend' => 'Cm_Cache_Backend_Redis',
                'backend_options' => [
                    'server' => '127.0.0.1',
                    'port' => '6379',
                    'database' => '1',
                    'id_prefix' => 'magento_prod_',
                    'compress_data' => '0',
                    'default_lifetime' => '86400',
                ],
            ],
        ],
    ],
];
```

- `database` keeps the two caches apart (`0` for the default cache, `1` for the full-page cache); `id_prefix` keeps keys unique when several sites share the instance.
- `default_lifetime` is the Time to Live (TTL) in seconds for keys with no explicit expiry. `min_lifetime` and `max_lifetime` bound the lifetime for the default cache.
- `compress_data` set to `1` shrinks the cache at a small processing cost. It is on for the default cache and off for the full-page cache, which is already compact.

After changing `env.php`, flush the cache:

```bash
php bin/magento cache:flush
```

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

```bash
php bin/magento cache:enable
```

## 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`:

```ini
[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):

```bash
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](../../technologies/system-services/manage-user-services.md#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](../../technologies/system-services/manage-user-services.md).

## 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](../../concepts/performance-tuning.md) 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](../../platform/hosts/backups.md) cover both the database and the `pub/media`, `app/etc`, and `var` directories.
- **Watch [Health](../../platform/hosts/health.md)** - 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.

## Related

- [Deploy Magento](deploy.md)
- [Magento reference](reference.md)
- [How to run multiple Magento store views](multiple-store-views.md)
- [Troubleshooting Magento](troubleshooting.md)
- [How to manage user system services](../../technologies/system-services/manage-user-services.md)
- [Services](../../platform/hosts/services.md)
- [Performance tuning](../../concepts/performance-tuning.md)
