Multi-Server and Scaling Architecture for Magento
AI generated
60fps
ms
Scaling · Architecture · Load Balancing · Magento 2
Multi-Server and Scaling Architecture for Magento
from one server to a resilient cluster

A single server inevitably hits limits as traffic grows, no matter how much RAM or CPU it gets. This article explains how Magento stores split web, database, search, and cache across dedicated servers, keep sessions centralized via Redis, deliver media consistently through NFS or S3, and run load balancers with proper health checks.

18 min. read Horizontal · Vertical · Redis · Shared Storage Magento 2.4.8 · Nginx · Varnish · Redis Cluster

1. When one server is no longer enough

As long as a Magento store runs on a single server, PHP-FPM, MySQL, Elasticsearch/OpenSearch, Redis, and the web server all share the same CPU, the same RAM, and the same disk I/O. At low traffic this works fine, because the resources are rarely fully utilized at the same time. But once concurrent orders, catalog indexing, and background reindexing overlap, all processes compete for the same resources, and response times increase for every user at once, not just for the ones causing the load.

A clear warning sign is when top or htop consistently show high I/O wait during normal traffic peaks while CPU and RAM still have headroom. That indicates MySQL and the web server are blocking each other over disk access. Before investing in more servers, it's always worth first checking caching, indexer mode, and query performance. A multi-server architecture doesn't fix a poorly optimized database schema, it just shifts the problem onto more hardware, which is expensive and not sustainable long term.

2. Horizontal vs. vertical scaling

Vertical scaling means giving an existing server more CPU cores, more RAM, or faster disks (NVMe instead of SATA SSD). The advantage: no architecture change needed, Magento keeps running unchanged. The downside: there's a physical and economic ceiling, and a single server remains a single point of failure. If it goes down, the entire store is offline, regardless of how powerful the hardware was.

Horizontal scaling means running multiple smaller or equally sized servers in parallel and distributing load through a load balancer. This increases not just capacity but also resilience: if a web node fails, the remaining nodes automatically absorb the traffic. The crucial catch: horizontal scaling requires the application to become stateless. Sessions, uploaded files, and caches can no longer live locally on a single server, they need to be centrally reachable. That single requirement drives the rest of the architecture in this article.

3. Separating web, database, search, and cache

The first step away from a single-server setup is rarely immediate horizontal scaling of web servers, but rather the functional separation of services onto dedicated servers: one or more application servers for PHP-FPM and Nginx, a dedicated MySQL/MariaDB server, a dedicated OpenSearch/Elasticsearch cluster for catalog search, and a dedicated Redis server for cache and sessions. Each service gets its own resources and can be scaled and monitored independently, without a heavy reindex job dragging down web request response times.

This separation is also the natural preparation step for horizontal scaling, because it forces configuration and access paths to already use hostnames instead of localhost. In app/etc/env.php, the database host, Redis host, and search engine host are entered explicitly as their own endpoints. Important in practice: network latency between services should stay under one millisecond within the data center, ideally within the same availability zone segment, otherwise the extra round-trip time per page load adds up noticeably, especially with multiple sequential Redis calls per request.


# Typical service split across dedicated hosts (example inventory)
# web-01, web-02      : Nginx + PHP-FPM (application/frontend nodes)
# db-01 (primary)     : MySQL/MariaDB write node
# db-02 (replica)     : MySQL/MariaDB read replica
# search-01..03       : OpenSearch cluster (3 nodes, quorum for split-brain safety)
# redis-cache-01      : Redis instance for full page cache + config cache
# redis-session-01    : Separate Redis instance for session storage

# Verify connectivity from a web node before cutting over app/etc/env.php
mysql -h db-01.internal -u magento -p -e "SELECT 1;"
redis-cli -h redis-session-01.internal -p 6379 PING
curl -s http://search-01.internal:9200/_cluster/health?pretty

4. Sessions in a multi-server setup: why Redis is mandatory

By default, PHP stores sessions as files on the local filesystem (/var/lib/php/sessions or similar). In a multi-server setup without sticky sessions, that's fatal: a user gets served by web-node-1 for request 1, and their session file only exists there. If request 2 lands on web-node-2 through the load balancer, PHP finds no session file there and the user gets unexpectedly logged out, the cart is empty, or checkout breaks mid-payment. This is one of the most common bugs in naive migrations from one server to several.

The solution is a central session store reachable from every web node, in the Magento world practically always Redis. All web nodes read and write sessions through the same Redis backend, regardless of which node serves the request. Magento supports this natively via the session.save_handler entry in app/etc/env.php. Important: session Redis and cache Redis should run on separate Redis instances or at least separate databases, so that a FLUSHALL during cache clearing doesn't accidentally wipe active user sessions. For high availability, Redis Sentinel or Redis Cluster is recommended, so that a failure of the primary Redis instance doesn't destroy every session at once.


// app/etc/env.php - Redis for sessions, separated from cache/FPC backend
return [
    'session' => [
        'save' => 'redis',
        'redis' => [
            'host' => 'redis-session-01.internal',
            'port' => '6379',
            'password' => '',
            'timeout' => '2.5',
            'persistent_identifier' => '',
            'database' => '2',           // dedicated DB index, separate from cache
            'compression_threshold' => '2048',
            'compression_library' => 'gzip',
            'log_level' => '1',
            'max_concurrency' => '20',    // concurrent lock holders per session
            'break_after_frontend' => '5',
            'break_after_adminhtml' => '30',
            'first_lifetime' => '600',
            'bot_first_lifetime' => '60',
            'bot_lifetime' => '7200',
            'disable_locking' => '0',      // keep locking enabled to avoid race conditions
        ],
    ],
    'cache' => [
        'frontend' => [
            'default' => [
                'backend' => 'Cm_Cache_Backend_Redis',
                'backend_options' => [
                    'server' => 'redis-cache-01.internal',
                    'port' => '6379',
                    'database' => '0',
                ],
            ],
            'page_cache' => [
                'backend' => 'Cm_Cache_Backend_Redis',
                'backend_options' => [
                    'server' => 'redis-cache-01.internal',
                    'port' => '6379',
                    'database' => '1',
                ],
            ],
        ],
    ],
];

5. Shared media storage: NFS vs. S3

Product images, uploaded customer files, and generated static assets land in pub/media. In a single-server setup, that's a local directory. Once multiple web nodes run in parallel, that directory has to be identical and consistent across all nodes, otherwise a freshly uploaded product image shows a 404 on node 1 the moment the next request gets served by node 2. Two established solutions: an NFS mount, where every web node mounts the same network filesystem, or object storage like S3 (or an S3-compatible alternative like MinIO), which Magento connects to via a media storage module.

NFS is simpler to set up and behaves for Magento like a regular local filesystem, but has downsides under high concurrency: locking overhead and a single point of failure at the NFS server itself, unless it's built redundantly. S3-based solutions are highly available by design and pair well with a CDN, but require an additional module (e.g. magento/module-aws-s3 in Adobe Commerce, or community extensions for Open Source) and a careful migration of existing media. For new multi-server deployments, S3 is usually the more robust choice; for existing on-premise infrastructure, a redundant NFS setup (e.g. via DRBD or a storage appliance) is often the more pragmatic first step.


# Option A: NFS mount for pub/media, identical on every web node
# On the NFS server (storage-01):
#   /etc/exports: /export/magento-media  10.0.1.0/24(rw,sync,no_subtree_check,no_root_squash)

# On each web node:
sudo mount -t nfs4 storage-01.internal:/export/magento-media /var/www/magento/pub/media
echo "storage-01.internal:/export/magento-media /var/www/magento/pub/media nfs4 defaults,_netdev 0 0" | sudo tee -a /etc/fstab

# Sanity check: file written on web-01 must appear instantly on web-02
touch /var/www/magento/pub/media/.nfs-writetest
ssh web-02 "ls -la /var/www/magento/pub/media/.nfs-writetest"

# Option B: S3-backed media storage (env.php snippet, module-dependent)
# 'remote_storage' => [
#     'driver' => 'aws-s3',
#     'config' => [
#         'bucket' => 'mironsoft-magento-media',
#         'region' => 'eu-central-1',
#     ],
# ],

6. Load balancer: configuring health checks correctly

A load balancer (Nginx, HAProxy, or a cloud LB like AWS ALB) distributes incoming requests across the available web nodes. The critical part is that the load balancer reliably detects broken nodes and pulls them out of rotation before users see error pages. A simple TCP check on port 443 isn't enough, because a server whose PHP-FPM has crashed can still keep the port open (Nginx responds, but only serves a 502 Bad Gateway). What's needed is an application-level health check that sends a real HTTP request against a dedicated endpoint and checks the response status as well as, ideally, the response time.

A good health check endpoint in Magento responds quickly but verifies the critical dependencies: database connectivity and Redis reachability. It should deliberately not go through the full page bootstrap, so the check itself doesn't become a source of load. Also important is a sensible interval: overly aggressive health checks (e.g. every 1 second with a short timeout) can falsely mark a node as down when it's briefly under load, causing flapping. An interval of 5 to 10 seconds with 2-3 consecutive failures before exclusion is a robust starting point.


# nginx.conf on the load balancer: upstream with active health checks
upstream magento_backend {
    zone magento_backend 64k;
    server web-01.internal:8080 max_fails=3 fail_timeout=10s;
    server web-02.internal:8080 max_fails=3 fail_timeout=10s;
    server web-03.internal:8080 max_fails=3 fail_timeout=10s backup;
}

server {
    listen 443 ssl;
    server_name mironsoft.de;

    location / {
        proxy_pass http://magento_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_next_upstream error timeout http_502 http_503;
    }

    # Application-level health check, not just a TCP handshake
    location /health/check {
        proxy_pass http://magento_backend/health_check.php;
        proxy_connect_timeout 2s;
        proxy_read_timeout 3s;
        access_log off;
    }
}

The health_check.php endpoint itself should return a compact, machine-readable JSON, so the load balancer can evaluate the HTTP status code while an external monitoring system logs the detailed response. A status code 200 signals that all checked dependencies are reachable, a 503 immediately pulls the node out of rotation.


{
  "status": "ok",
  "checks": {
    "database": { "status": "ok", "latency_ms": 4 },
    "redis_session": { "status": "ok", "latency_ms": 1 },
    "redis_cache": { "status": "ok", "latency_ms": 1 }
  },
  "node": "web-02.internal",
  "timestamp": "2026-07-11T09:12:44+00:00"
}

7. Sticky sessions vs. stateless web nodes

Sticky sessions (session affinity) mean the load balancer always routes the same client to the same web node, based on a cookie or the client IP. At first glance a simple fix for the session problem, but it comes with its own downsides: if the assigned node fails, the user still loses their session, since it was stored locally on exactly that node. On top of that, load distributes less evenly, because users with long sessions stay stuck on one node instead of being dynamically balanced across available capacity.

The more robust architecture, as described in section 4, makes web nodes fully stateless: sessions live in Redis, media lives in NFS/S3, and any request can be served by any node. That makes sticky session affinity unnecessary, the load balancer can use true round-robin or least-connections balancing without constraints, and a failing node doesn't affect a single active user, since the next request simply gets picked up by another node. In practice, stateless architecture with centralized Redis session storage is almost always the better choice over sticky sessions, except in scenarios with very specific WebSocket or long-polling requirements.

8. Database scaling: replication and read splitting

While web nodes can be scaled horizontally relatively easily, the database is usually the hardest part of the architecture, because MySQL/MariaDB isn't natively designed for simultaneous horizontal writes across multiple nodes. The common approach: a primary server for write operations and one or more read replicas for read operations, connected via asynchronous or semi-synchronous replication. Magento natively supports read/write splitting through the db configuration in app/etc/env.php, where separate connection definitions can be set for default (writes) and checkout/sales (reads).

Important to understand: asynchronous replication means read replicas can lag slightly behind the primary server (replication lag). For non-critical read operations like catalog queries, that's usually harmless; for processes right after a write, such as immediately reading back an order that was just created in the checkout flow, it can lead to inconsistent results if that read accidentally targets the replica instead of the primary. Critical reads during checkout should therefore explicitly run against the primary connection, not the replica.

Approach Problem Recommended solution Effect
Session storage Local PHP file sessions Centralized Redis backend User stays logged in, regardless of which node responds
Media storage Local pub/media per server NFS mount or S3 backend Consistent images across all nodes
Load balancing Sticky sessions via cookie/IP Stateless nodes, unrestricted balancing More even load distribution, no session loss
Health check Plain TCP port check HTTP health endpoint with DB/Redis check Broken nodes are reliably detected
Database A single MySQL server Primary + read replicas, read/write splitting Higher read throughput, fewer single points of failure

The table shows a recurring pattern: almost every scaling problem in Magento comes from a silent assumption that one server is enough for everything. The fix is almost always the same core idea, namely centralizing state and keeping the rest of the architecture stateless, so horizontal scaling works without hidden side effects.

Mironsoft

Cluster architecture, Redis sessions, and load balancer configuration for Magento

Ready for more than one server?

We plan and build your multi-server architecture: session storage, shared media, load balancer health checks, and database replication, cleanly separated and without hidden single points of failure.

Architecture audit

Analysis of existing infrastructure and scaling bottlenecks

Session & storage migration

Introducing Redis sessions and NFS/S3 media storage without downtime

Load balancer setup

Health checks, failover strategies, and capacity planning

10. Summary

Multi-server and scaling architecture for Magento solves one central problem: a single server has a hard capacity ceiling and is simultaneously a single point of failure. Vertical scaling pushes that ceiling upward, horizontal scaling removes it structurally, but requires web nodes to become stateless. Sessions belong centrally in Redis, media belongs in a shared store like NFS or S3, and the database benefits from read replicas with clean read/write splitting for reads outside of critical checkout paths.

The load balancer is the connective piece of this architecture and needs genuine application-level health checks, not just a TCP port check, so broken nodes are reliably pulled from rotation. Sticky sessions are no longer necessary once session storage is properly centralized, and should be avoided, since they make load distribution uneven and still lose sessions on failure. Get these building blocks right, and web nodes can be added or removed on demand without users noticing a thing.

Multi-Server and Scaling Architecture for Magento - The Essentials at a Glance

Horizontal over vertical

More servers of similar size instead of one ever-bigger server. Requires stateless web nodes.

Sessions centralized in Redis

Never use local PHP file sessions in a multi-server setup. Separate Redis instance or DB index for sessions.

Shared media storage

NFS mount or S3 backend for pub/media, so all nodes see the same images.

Health checks & read replicas

Application-level health checks instead of a plain TCP check. Read/write splitting for database scaling.

11. FAQ: Multi-Server and Scaling Architecture for Magento

1What is the difference between horizontal and vertical scaling?
Vertical: more resources for an existing server. Horizontal: load spread across multiple servers. Horizontal also increases resilience but requires stateless web nodes.
2Why do I need Redis for sessions?
Local PHP file sessions are isolated per server. If a different node serves the next request, the session is missing. Redis as a central store solves this reliably.
3Should session Redis and cache Redis use the same instance?
Better not: a FLUSHALL during cache clearing would accidentally wipe all sessions too. Separate instances or DB indexes avoid this.
4NFS or S3 for Magento media?
NFS behaves like a local directory but has locking overhead and is a single point of failure without redundancy. S3 is highly available and CDN-friendly, but needs a storage module.
5What are sticky sessions and why avoid them?
Always route clients to the same node. If it fails, the session is lost anyway, and load distribution becomes uneven. Unnecessary with Redis sessions.
6What should a health check for Magento look like?
An application-level check against a dedicated endpoint that verifies DB and Redis reachability, instead of just testing the TCP port.
7How often should a health check run?
A 5 to 10 second interval with 2-3 failures before exclusion is robust. Overly aggressive checks cause flapping.
8What is read/write splitting?
Writes go to the primary server, reads go to read replicas. Critical reads right after a write should still target the primary due to replication lag.
9Does multi-server architecture fix all performance problems?
No. A poor database schema or missing cache stays inefficient, only more expensive. Check caching and query performance first, then scale.
10How much network latency between services is acceptable?
Ideally under one millisecond within the same data center. Higher latency adds up noticeably with multiple sequential Redis/DB calls per request.