The Production Stack

The services a Magento 2.4.9 store needs, what each one does, and how the shipped nginx.conf.sample wires the web tier.

Syntaxset $MAGE_ROOT /var/www/magento; include /var/www/magento/nginx.conf.sample;

Magento is a PHP application that cannot run on its own. A production store is a handful of services, each with one job. Most reports of "Magento is broken" are one of them missing, or pointed at the wrong host.

What each service is for

  • nginx serves static files and hands PHP requests to PHP-FPM. Its document root is the pub/ directory, not the project root.
  • PHP-FPM runs the application. Memory limits, execution limits and opcache settings live in its pool, not in Magento.
  • MySQL or MariaDB holds the data and is the only writer.
  • OpenSearch answers catalogue search and layered navigation. Magento 2.4 will not install without a search engine, and 2.4.9 is supported against OpenSearch 3.x; OpenSearch 2.x was last listed as supported for 2.4.7.
  • Redis, or Valkey, which speaks the same protocol, stores the caches and PHP sessions so several web nodes see the same data.
  • RabbitMQ carries asynchronous messages to consumers, the long-running PHP processes that read a queue. Without a broker, Magento keeps those messages in MySQL tables instead.

The sample nginx config

Magento ships nginx.conf.sample in the project root, and you include it rather than copy it. Your server block sets $MAGE_ROOT and includes the file; the sample then supplies root $MAGE_ROOT/pub, the allowed PHP entry points (index.php, get.php, static.php, health_check.php), the /static/ and /media/ rules, and the denies. Changes take effect with nginx -t and a reload; no bin/magento command touches nginx.

What trips people up

Rooting the server at the project instead of pub/ publishes app/etc/env.php with your database password in it. And after editing env.php, run bin/magento cache:flush before judging the result.

Example

Example · nginx
# /etc/nginx/conf.d/magento.conf
upstream fastcgi_backend {
    server unix:/run/php/php8.4-fpm.sock;
}

server {
    listen 80;
    server_name store.example.com;

    set $MAGE_ROOT /var/www/magento;
    client_max_body_size 32m;

    # The sample supplies root $MAGE_ROOT/pub, the PHP entry points
    # (index.php, get.php, static.php, health_check.php), the /static/
    # and /media/ handling, and the denies for app/, var/ and vendor/.
    include /var/www/magento/nginx.conf.sample;
}

# Apply with: nginx -t && systemctl reload nginx
# Then check: curl -I http://store.example.com/health_check.php

When to use it

  • Write the nginx server block for a new store before the first deploy.
  • Work out which service is down when the storefront 500s but the admin login page renders.
  • Decide whether a project needs RabbitMQ or can stay on the MySQL message queue.
  • Hand a hosting provider the exact service list and versions a 2.4.9 store requires.

More examples

The PHP-FPM pool a storefront node needs

Magento needs a large memory limit for CLI-sized work in web requests, and an opcache big enough for tens of thousands of generated files. With validate_timestamps off, PHP never notices that files changed, so a deploy is only live after systemctl reload php8.4-fpm. Reach for this when a release appears to have no effect, or when the admin times out on save.

Example · ini
; /etc/php/8.4/fpm/pool.d/magento.conf
[magento]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
listen.owner = www-data

pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 500

php_admin_value[memory_limit] = 2G
php_admin_value[max_execution_time] = 1800
php_admin_flag[opcache.enable] = On
php_admin_value[opcache.memory_consumption] = 512
php_admin_value[opcache.max_accelerated_files] = 60000
php_admin_flag[opcache.validate_timestamps] = Off
php_admin_value[realpath_cache_size] = 10M
php_admin_value[realpath_cache_ttl] = 7200

Pointing an install at OpenSearch, RabbitMQ and Redis

Every service address a store depends on is set here and lands in app/etc/env.php. The three Redis uses take different database numbers on purpose: flushing the page cache must not sign customers out. Use this when standing up a new environment, or when comparing staging against production by diffing env.php.

Example · bash
# Run from the Magento root, as the file system owner. Admin flags omitted.
bin/magento setup:install \
  --base-url=https://store.example.com/ \
  --db-host=mysql-primary.internal --db-name=magento \
  --db-user=magento --db-password="$DB_PASSWORD" \
  --search-engine=opensearch \
  --opensearch-host=opensearch.internal --opensearch-port=9200 \
  --opensearch-index-prefix=store --opensearch-timeout=15 \
  --amqp-host=rabbitmq.internal --amqp-port=5672 \
  --amqp-user=magento --amqp-password="$AMQP_PASSWORD"

# Caches and sessions afterwards. Each writes app/etc/env.php.
bin/magento setup:config:set --cache-backend=redis \
  --cache-backend-redis-server=redis.internal --cache-backend-redis-db=0
bin/magento setup:config:set --page-cache=redis \
  --page-cache-redis-server=redis.internal --page-cache-redis-db=1
bin/magento setup:config:set --session-save=redis \
  --session-save-redis-host=redis.internal --session-save-redis-db=2

bin/magento cache:flush

Checking whether each service answers

Magento ships pub/health_check.php, which boots the framework, connects to each configured database and tests each cache backend. A 200 narrows an outage to the search engine, the broker or the code; a 500 points at the database or the cache. Keep this list in the runbook so the first five minutes of an incident are not spent inventing it.

Example · bash
# PHP, every configured database and every cache backend, in one request:
# health_check.php returns 500 if any of them fails.
curl -s -o /dev/null -w '%{http_code}\n' https://store.example.com/health_check.php

redis-cli -h redis.internal ping
curl -s http://opensearch.internal:9200/_cluster/health | head -c 120
mysql -h mysql-primary.internal -u magento -p -e 'SELECT 1'
sudo rabbitmqctl list_queues name messages | head

# From inside the application, for the parts curl cannot see:
bin/magento cache:status
bin/magento indexer:status
bin/magento queue:consumers:list

Discussion

  • Be the first to comment on this lesson.