Building a High-Availability, Cost-Optimized Magento 2 Stack on OVH
OVHcloud Infrastructure for Magento 2: A Cost-Optimized HA Architecture
This document outlines a high-availability (HA) and cost-optimized Magento 2 architecture leveraging OVHcloud’s infrastructure. The focus is on achieving resilience and performance without incurring the premium costs often associated with managed cloud providers. We’ll detail the server roles, network configuration, database setup, caching strategies, and essential deployment practices.
Server Roles and Instance Selection
A robust Magento 2 deployment requires distinct roles for optimal performance and scalability. For cost-effectiveness on OVHcloud, we’ll utilize their “Public Cloud” instances, specifically focusing on the “General Purpose” (B2) and “RAM Optimized” (R2) series for compute and memory-intensive tasks, respectively. Storage will be handled by OVHcloud’s block storage (SSD) for performance.
- Web Servers (2x): Handle incoming HTTP/S traffic, serve static assets, and proxy to PHP-FPM. These should be General Purpose instances (e.g.,
b2-7or similar) with sufficient CPU and RAM. - PHP-FPM Servers (2x): Execute PHP code. These can also be General Purpose instances, potentially scaled down if web servers are powerful enough, or dedicated RAM Optimized instances (e.g.,
r2-7) if PHP processing is a bottleneck. - Database Server (1x HA Pair): A highly available MySQL setup. We’ll use two instances for replication and failover. RAM Optimized instances (e.g.,
r2-15or larger) are recommended due to MySQL’s memory-intensive nature. - Redis Cache Server (1x HA Pair): For session storage and full-page caching. Similar to the database, a pair of instances (e.g.,
r2-7) configured for Sentinel-based HA is ideal. - Elasticsearch Server (1x HA Cluster): For search functionality. Depending on data volume, this might require dedicated instances or a cluster of smaller instances. For simplicity in this HA setup, we’ll assume a single powerful instance (e.g.,
r2-15) for now, with a note on scaling to a cluster. - Load Balancer (1x): Distributes traffic across web servers. OVHcloud’s “Load Balancer” service is a cost-effective managed option.
Network Configuration and Security
OVHcloud’s Public Cloud networking provides the necessary tools. We’ll use a private network (VPC) to isolate internal services and expose only necessary ports to the public internet via the load balancer.
- VPC Setup: Create a dedicated Virtual Private Cloud (VPC) for all Magento-related instances.
- Security Groups/Firewall Rules:
- Allow HTTP (80) and HTTPS (443) from the internet to the Load Balancer.
- Allow traffic from the Load Balancer to Web Servers on port 80.
- Allow traffic from Web Servers to PHP-FPM Servers on port 9000.
- Allow traffic from PHP-FPM Servers to Database Servers on port 3306.
- Allow traffic from Web Servers and PHP-FPM Servers to Redis Servers on port 6379.
- Allow traffic from Web Servers and PHP-FPM Servers to Elasticsearch Servers on port 9200.
- Allow SSH (22) access only from trusted IP ranges (e.g., your office VPN or bastion host).
- DNS: Configure A records pointing to the OVHcloud Load Balancer’s public IP address.
Web Server and PHP-FPM Setup (Nginx + PHP-FPM)
We’ll use Nginx as the web server and PHP-FPM for PHP execution. This setup will be deployed on both web and PHP-FPM server roles.
Nginx Configuration (Web Servers)
The Nginx configuration on the web servers will act as a reverse proxy to PHP-FPM and serve static assets directly. For HA, ensure both web servers have identical configurations.
/etc/nginx/sites-available/magento2
server {
listen 80;
server_name your_domain.com www.your_domain.com;
root /var/www/html/public; # Magento 2 public directory
index index.php;
# SSL configuration (if using direct SSL on web servers, otherwise handled by LB)
# listen 443 ssl http2;
# ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ ^/static/version.*\.js$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
location ~ ^/static/frontend/.*\.css$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
location ~ ^/media/.*\.jpg$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version as needed
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Magento 2 specific FastCGI params
fastcgi_param MAGE_RUN_CODE "your_store_code"; # Replace with your Magento store code
fastcgi_param MAGE_RUN_TYPE "store"; # Or "website"
fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
fastcgi_param HTTP_X_FORWARDED_HOST $host;
fastcgi_param HTTP_HOST $host;
fastcgi_param HTTP_X_REAL_IP $remote_addr;
fastcgi_param HTTP_CLIENT_IP $remote_addr;
}
location ~ /\.ht {
deny all;
}
# Deny access to sensitive files
location ~* /(composer\.json|composer\.lock|\.env|\.git|\.svn|var/cache|var/session|var/log) {
deny all;
}
}
PHP-FPM Configuration (PHP-FPM Servers)
Ensure PHP-FPM is configured to handle concurrent requests efficiently. The pool configuration is critical.
/etc/php/8.1/fpm/pool.d/www.conf (Example for PHP 8.1)
; Adjust pm.max_children based on server RAM and expected load. ; A common starting point is (Total RAM - 1GB) / Average Process Size. ; Monitor with `htop` and `php-fpm -t`. pm = dynamic pm.max_children = 100 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 pm.process_idle_timeout = 10s ; Adjust request_terminate_timeout for long-running Magento tasks (e.g., cron) ; Magento recommends at least 600s for CLI commands. For FPM, this is less critical but can prevent timeouts. request_terminate_timeout = 300 ; Set user and group to the web server user user = www-data group = www-data ; Listen on a Unix socket for better performance and security listen = /var/run/php/php8.1-fpm.sock ; Set appropriate permissions for the socket listen.owner = www-data listen.group = www-data listen.mode = 0660 ; Increase memory limit for Magento memory_limit = 512M ; Increase max_execution_time for CLI scripts (though FPM has request_terminate_timeout) max_execution_time = 300 ; Increase post_max_size and upload_max_filesize if large file uploads are common post_max_size = 64M upload_max_filesize = 64M ; Enable opcache for performance [opcache] opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=10000 opcache.revalidate_freq=2 opcache.validate_timestamps=1 opcache.enable_cli=1 opcache.file_cache_dir=/var/cache/php-fpm-opcache opcache.file_cache_default_permissions=0666 opcache.file_cache_fallback=1 opcache.huge_code_pages=1
Important: Ensure the opcache.file_cache_dir exists and is writable by the www-data user. Create it with sudo mkdir -p /var/cache/php-fpm-opcache && sudo chown www-data:www-data /var/cache/php-fpm-opcache.
High-Availability MySQL Setup
For HA, we’ll implement MySQL replication with a primary and a replica. OVHcloud’s block storage (SSD) is crucial here for performance. We’ll use Percona XtraDB Cluster or Galera Cluster for true multi-master HA if budget allows, but for cost optimization, a primary-replica setup with automated failover is a good compromise.
Primary MySQL Server Configuration
On the primary MySQL instance:
/etc/mysql/mysql.conf.d/mysqld.cnf (Primary)
[mysqld] server-id = 1 log_bin = /var/log/mysql/mysql-bin.log binlog_format = ROW relay_log = /var/log/mysql/mysql-relay-bin.log read_only = OFF bind-address = 0.0.0.0 ; Listen on all interfaces within the private network # InnoDB settings for performance innodb_buffer_pool_size = 8G ; Adjust based on instance RAM (e.g., 50% of total RAM) innodb_log_file_size = 1G innodb_flush_log_at_trx_commit = 1 innodb_flush_method = O_DIRECT innodb_file_per_table = ON # Replication user (create this user on the primary) # CREATE USER 'repl_user'@'%' IDENTIFIED BY 'your_replication_password'; # GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%'; # FLUSH PRIVILEGES; # GTID settings (optional but recommended for easier failover) # gtid_mode = ON # enforce_gtid_consistency = ON # Other performance tuning parameters max_connections = 500 query_cache_type = 0 query_cache_size = 0 tmp_table_size = 64M max_heap_table_size = 64M sort_buffer_size = 2M join_buffer_size = 2M read_rnd_buffer_size = 1M thread_cache_size = 16 table_open_cache = 2000 table_definition_cache = 1000
Replica MySQL Server Configuration
On the replica MySQL instance:
/etc/mysql/mysql.conf.d/mysqld.cnf (Replica)
[mysqld] server-id = 2 log_bin = /var/log/mysql/mysql-bin.log binlog_format = ROW relay_log = /var/log/mysql/mysql-relay-bin.log read_only = ON ; Crucial for replica bind-address = 0.0.0.0 ; Listen on all interfaces within the private network # InnoDB settings (should match primary) innodb_buffer_pool_size = 8G innodb_log_file_size = 1G innodb_flush_log_at_trx_commit = 1 innodb_flush_method = O_DIRECT innodb_file_per_table = ON # GTID settings (if enabled on primary) # gtid_mode = ON # enforce_gtid_consistency = ON # Other performance tuning parameters (should match primary) max_connections = 500 query_cache_type = 0 query_cache_size = 0 tmp_table_size = 64M max_heap_table_size = 64M sort_buffer_size = 2M join_buffer_size = 2M read_rnd_buffer_size = 1M thread_cache_size = 16 table_open_cache = 2000 table_definition_cache = 1000
Replication Setup Steps
- On Primary:
- Create replication user:
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'your_replication_password'; GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%'; FLUSH PRIVILEGES; - Get binary log position:
SHOW MASTER STATUS;(NoteFileandPosition).
- Create replication user:
- On Replica:
- Stop MySQL:
sudo systemctl stop mysql - Configure
server-idandread_only=ONinmysqld.cnf. - Start MySQL:
sudo systemctl start mysql - Configure replication:
CHANGE MASTER TO MASTER_HOST='', MASTER_USER='repl_user', MASTER_PASSWORD='your_replication_password', MASTER_LOG_FILE='mysql-bin.XXXXXX', -- From SHOW MASTER STATUS MASTER_LOG_POS=YYYYYY; -- From SHOW MASTER STATUS -- If using GTID: -- MASTER_AUTO_POSITION=1; - Start replication:
START SLAVE; - Verify replication:
SHOW SLAVE STATUS\G(EnsureSlave_IO_Running: YesandSlave_SQL_Running: Yes, andSeconds_Behind_Masteris low).
- Stop MySQL:
- For HA Failover: Implement a monitoring script or use a tool like Orchestrator or MHA to detect primary failure and promote the replica. This is outside the scope of basic configuration but essential for true HA.
Redis for Caching and Sessions
Redis is critical for Magento 2 performance. We’ll set up a Redis Sentinel cluster for high availability.
Redis Master/Replica Configuration
On both Redis instances (let’s call them redis-1 and redis-2):
/etc/redis/redis.conf
# General settings port 6379 daemonize yes pidfile /var/run/redis/redis-server.pid logfile /var/log/redis/redis-server.log dir /var/lib/redis # Network settings bind 0.0.0.0 ; Bind to private network interface protected-mode no ; Required if not using password and binding to 0.0.0.0 # Persistence (disable for cache-only, or configure appropriately if needed) save "" appendonly no # Replication settings replica-serve-stale-data yes replica-read-only yes replica-announce-ip; Announce this instance's IP replica-announce-port 6379 # Sentinel configuration (on separate Sentinel instances or same as Redis) # If running Sentinel on the same nodes, uncomment and configure these: # sentinel monitor mymaster 6379 2 # sentinel down-after-milliseconds mymaster 6000 # sentinel failover-timeout mymaster 180000 # sentinel parallel-syncs mymaster 1 # sentinel notification-script mymaster /etc/redis/notify-script.sh # sentinel client-reconfig-script mymaster /etc/redis/reconfig-script.sh
Redis Sentinel Configuration
Set up at least two Sentinel instances (can be on the same servers as Redis, or dedicated small instances). Let’s assume they are on redis-1 and redis-2.
/etc/redis/sentinel.conf (on both Sentinel nodes)
port 26379 daemonize yes pidfile /var/run/redis/sentinel.pid logfile /var/log/redis/sentinel.log dir /var/lib/redis # Monitor the master, require 2 sentinels to agree it's down, failover after 3 minutes sentinel monitor mymaster6379 2 sentinel down-after-milliseconds mymaster 6000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1 # Optional: Notification and reconfig scripts for automation # sentinel notification-script mymaster /etc/redis/notify-script.sh # sentinel client-reconfig-script mymaster /etc/redis/reconfig-script.sh
Setup Steps:
- Install Redis server and Sentinel packages.
- Configure
redis.confon both nodes, settingreplica-announce-ipcorrectly. - Configure
sentinel.confon both nodes, pointing to the *initial* master IP. - Start Redis server and Sentinel services on both nodes.
- On the *initial* master node (e.g.,
redis-1), configure it as master:sudo redis-cli -p 6379 replicaof no one
- On the *initial* replica node (e.g.,
redis-2), configure it as replica:sudo redis-cli -p 6379 replicaof
6379 - Verify Sentinel status:
redis-cli -p 26379 SENTINEL master mymasterandredis-cli -p 26379 SENTINEL replicas mymaster.
Magento 2 Configuration (app/etc/env.php)
Update your Magento env.php to use the Sentinel master address.
<?php
return [
'backend' => [
'frontName' => 'admin_your_admin_path'
],
'crypt' => [
'key' => 'your_crypt_key'
],
'db' => [
'table_prefix' => '',
'connection' => [
'default' => [
'host' => '',
'dbname' => 'magento_db',
'username' => 'magento_user',
'password' => 'magento_password',
'model' => 'mysql4',
'initStatements' => 'SET NAMES utf8',
'engine' => 'innodb',
'active' => 1
],
'slave' => [ // For read-only queries, if using separate read replicas
'host' => '',
'dbname' => 'magento_db',
'username' => 'magento_user',
'password' => 'magento_password',
'model' => 'mysql4',
'initStatements' => 'SET NAMES utf8',
'engine' => 'innodb',
'active' => 1
]
]
],
'resource' => [
'default_setup' => [
'connection' => 'default'
],
'default_read' => [
'connection' => 'slave' // Point read operations to slave
]
],
'cache' => [
'frontend' => [
'default' => [
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'options' => [
'server' => '', // e.g., 'mymaster'
'port' => '26379', // Sentinel port
'database' => '0',
'password' => '',
'sentinel_master_name' => '', // e.g., 'mymaster'
'sentinel_port' => '26379'
]
],
'page_cache' => [
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'options' => [
'server' => '', // e.g., 'mymaster'
'port' => '26379', // Sentinel port
'database' => '1',
'password' => '',
'sentinel_master_name' => '', // e.g., 'mymaster'
'sentinel_port' => '26379'
]
]
]
]
];
?>
Note: For session storage, configure app/etc/env.php under the session key to point to Redis similarly.
Elasticsearch Integration
Elasticsearch is essential for Magento 2’s catalog search. For cost optimization, a single, well-provisioned instance is a starting point. For true HA, a multi-node cluster is required.
Elasticsearch Instance Configuration
Ensure Elasticsearch is installed and accessible on its default port (9200) within the private network. Tune JVM heap size based on instance RAM. A common recommendation is 50% of available RAM, up to a maximum of 30-32GB.
/etc/elasticsearch/jvm.options (Example)
-Xms4g -Xmx4g
Magento 2 Configuration:
In the Magento Admin panel: Stores > Configuration > Catalog > Elasticsearch. Configure the connection details to point to your Elasticsearch instance’s private IP address.
Load Balancer Configuration (OVHcloud Managed LB)
OVHcloud’s Load Balancer service simplifies traffic distribution and SSL termination.
- Frontend Configuration:
- Protocol: HTTP/HTTPS
- Port: 80/443
- SSL Certificate: Upload your SSL certificate.
- Backend Pool:
- Add your two Web Server instances.
- Protocol: HTTP
- Port: 80
- Health Check: Configure an HTTP health check (e.g., GET /healthz or a specific Magento endpoint) to monitor backend server availability.
- Load Balancing Algorithm: Round Robin is a good default.
Deployment and Maintenance
Deployment Workflow
- Code Deployment: Use a CI/CD pipeline (e.g., GitLab CI, GitHub Actions, Jenkins) to deploy code to all web and PHP-FPM servers. Ensure consistent deployment across all nodes.
- Magento Commands: Run Magento CLI commands (e.g.,
setup:upgrade,cache:flush,indexer:reindex) from a single management server or via SSH to one of the web servers. For HA, ensure commands are run on all relevant nodes or that the deployment process handles this. - File Permissions: Maintain consistent file permissions across all web/PHP-FPM servers.
Cron Jobs
Magento cron jobs are critical. For HA, you should run cron jobs on only *one* server at a time to avoid duplicate processing. Use a distributed locking mechanism or a simple flag file on a shared volume (if available and reliable) or manually manage which server runs cron.
Example Cron Setup (on one designated server)
On the designated cron server, add entries to crontab -e:
# Magento 2 Cron Jobs * * * * * /usr/bin/php /var/www/html/bin/magento cron:run >> /var/log/magento/cron.log 2>&1 * * * * * /usr/bin/php /var/www/html/update/cron.php >> /var/log/magento/update_cron.log 2>&1 * * * * * /usr/bin/php /var/www/html/bin/magento setup:cron:run >> /var/log/magento/setup_cron.log 2>&1
Ensure the log directory /var/log/magento exists and is writable by the web server user.
Monitoring and Alerting
Implement robust monitoring for all components:
- Server Resources: CPU, RAM, Disk I/O, Network traffic (e.g., using Prometheus/Grafana, Zabbix, or OVHcloud’s monitoring tools).
- Application Performance: Magento-specific metrics, error rates, response times (e.g., New Relic, Datadog, or custom logging).
- Database: Replication status, query performance, connection counts.
- Redis: Memory usage, connected clients, latency.
- Elasticsearch: Cluster health, indexing speed, query latency.
- Load Balancer: Backend health status.
Cost Optimization Strategies
OVHcloud’s pricing model allows for significant cost savings compared to hyperscalers. Key strategies include:
- Right-Sizing Instances: Continuously monitor resource utilization and adjust instance types. Start with smaller instances and scale up as needed.
- Reserved Instances/Savings Plans: If you have predictable long-term usage, explore OVHcloud’s options for commitment-based discounts.
- Storage Optimization: Use SSD block storage for performance-critical databases and caches. For less critical data (e.g., logs, backups), consider cheaper storage options if available.
- Managed Services: Leverage OVHcloud’s managed Load Balancer instead of self-hosting HAProxy/Nginx for load balancing.
- Auto-Scaling (if applicable): While not detailed here for simplicity, consider OVHcloud’s auto-scaling capabilities for web/PHP-FPM tiers during peak traffic periods to dynamically adjust capacity and reduce costs during off-peak hours.
- Instance Spot Market: For stateless workloads like web servers (if not requiring persistent local storage), consider OVHcloud’s spot instances for significant cost reductions, but ensure your architecture can handle potential interruptions.
Conclusion
This architecture provides a solid foundation for a high-availability Magento 2 deployment on OVHcloud, emphasizing cost-effectiveness. By carefully selecting instance types, configuring services for resilience (replication, Sentinel), and implementing robust monitoring, CTOs and VPs of Engineering can achieve a performant and reliable e-commerce platform while managing infrastructure spend effectively.