The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on AWS for Magento 2
Nginx Tuning for Magento 2 on AWS
Optimizing Nginx for a high-traffic Magento 2 instance on AWS requires a granular approach, focusing on connection handling, caching, and efficient static file serving. We’ll leverage AWS EC2 instances, potentially with an Elastic Load Balancer (ELB) in front.
Worker Processes and Connections
The `worker_processes` directive should ideally be set to the number of CPU cores available to the Nginx worker processes. For optimal performance, especially with multiple CPU cores, setting it to `auto` is often sufficient, allowing Nginx to determine the best value. The `worker_connections` directive dictates the maximum number of simultaneous connections that each worker process can handle. This value, combined with `worker_processes`, determines the total maximum connections Nginx can manage. A common starting point is 1024, but this can be increased significantly based on your server’s RAM and expected load.
Consider the `ulimit -n` setting on your operating system. Nginx’s `worker_connections` should not exceed `ulimit -n / worker_processes`. Ensure your system’s file descriptor limits are adequately set.
Nginx Configuration Snippet
worker_processes auto;
events {
worker_connections 4096; # Adjust based on ulimit -n and expected load
multi_accept on;
}
http {
# ... other http directives ...
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off; # Important for security
# ... other http directives ...
}
Caching Strategies
Nginx’s built-in FastCGI cache is crucial for reducing backend load. Magento 2 benefits immensely from page caching, and Nginx can serve cached responses directly, bypassing Gunicorn/PHP-FPM for many requests.
FastCGI Cache Configuration
Define a cache zone and then configure your Magento 2 location block to utilize it. Ensure the cache path has appropriate permissions and is on a fast storage medium (e.g., an EBS volume with good IOPS).
http {
# ... other http directives ...
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=magento_cache:100m inactive=60m max_size=10g;
fastcgi_temp_path /var/tmp/nginx/fastcgi_temp;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
fastcgi_cache_valid 404 1m; # Cache 404s for 1 minute
fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
# ... other http directives ...
server {
# ... server configuration ...
location / {
# ... other location directives ...
try_files $uri $uri/ /index.php?$args;
# Magento 2 specific FastCGI cache
set $skip_cache 0;
# Don't cache requests that don't look like static files
if ($request_uri ~* "\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg)$") {
set $skip_cache 1;
}
# Don't cache POST requests or requests with specific cookies
if ($request_method = POST) {
set $skip_cache 1;
}
if ($http_cookie ~* "PHPSESSID|mage-cache-sessid|mage-cache-storage|mage-cache-storage-user") {
set $skip_cache 1;
}
# Don't cache requests for admin or AJAX
if ($request_uri ~* "^/admin/|^/ajax/") {
set $skip_cache 1;
}
# Apply cache only if not skipped
if ($skip_cache = 0) {
add_header X-FastCGI-Cache "HIT";
fastcgi_cache magento_cache;
fastcgi_cache_bypass $skip_cache;
fastcgi_cache_revalidate 1;
}
# ... FastCGI pass directives ...
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP-FPM version and socket path
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param MAGE_RUN_CODE "your_store_code"; # Set your Magento store code
fastcgi_param MAGE_RUN_TYPE "store"; # Set to 'store' or 'website'
}
# ... other location blocks for static files ...
}
}
Static File Serving
Nginx excels at serving static assets. Configure appropriate `expires` headers and `gzip` compression to optimize delivery. Magento 2’s static content deployment (`bin/magento setup:static-content:deploy`) should be performed regularly.
location ~* ^/(media|static)/ {
expires 30d;
add_header Cache-Control "public";
gzip_static on; # Requires pre-gzipped files
access_log off;
log_not_found off;
}
Gunicorn/PHP-FPM Tuning for Magento 2
The application server (Gunicorn for Python-based frameworks, or PHP-FPM for PHP) is the next critical layer. For Magento 2, we’re primarily concerned with PHP-FPM.
PHP-FPM Process Management
PHP-FPM offers several process management strategies: `static`, `dynamic`, and `ondemand`. For Magento 2, `dynamic` is often a good balance, allowing the number of child processes to scale up to a defined `pm.max_children` but also scale down when idle. `static` can offer more predictable performance but requires careful tuning of `pm.max_children` to avoid over-provisioning or under-provisioning.
PHP-FPM Configuration (`php-fpm.conf` or `pool.d/www.conf`)
; Choose one of the process management modes: static, dynamic or ondemand. pm = dynamic ; The number of child processes to be created when pm = static. ; pm.max_children = 50 ; Example for static ; The desired number of child processes to be available at any time. ; Used with pm = dynamic. pm.max_children = 100 ; Adjust based on server RAM and expected load ; The number of *additional* child processes which will be spawned when the ; number of *currently running* child processes reaches this limit. ; Used with pm = dynamic. pm.start_servers = 10 ; Start with 10 servers ; The minimum number of children to be kept active. The FPM will gently kill ; existing children if the number of children is above this value. ; Used with pm = dynamic. pm.min_spare_servers = 5 ; Keep at least 5 servers ; The maximum number of children to be kept active. The FPM will gently kill ; existing children if the number of children is above this value. ; Used with pm = dynamic. pm.max_spare_servers = 20 ; Don't let idle servers exceed 20 ; The maximum number of requests which will be processed by a child process. ; Set to 0 to disable this feature. ; This can be useful to prevent memory leaks in third-party libraries. ; For Magento, a higher value might be acceptable if memory leaks are managed. pm.max_requests = 500 ; The amount of time in seconds after which PHP script will be terminated. ; Magento can have long-running processes, especially during cron jobs or ; complex catalog operations. Increase this value cautiously. ; Default is 30. ; Consider setting this higher for specific cron tasks if needed, but be mindful of timeouts. ; request_terminate_timeout = 60 ; The number of seconds after which PHP script will be terminated. ; Use this if you are using pm = ondemand. ; pm.process_idle_timeout = 10s ; For ondemand mode ; The maximum CPU number that can be used by the pool. ; pm.max_children = 5 ; Example for multi-core systems with limited resources ; pm.max_children = 100 ; For systems with ample RAM and CPU cores
Tuning `pm.max_children`: This is the most critical setting. A common formula is `(Total RAM – RAM for OS/other services) / Memory per PHP process`. Memory per PHP process can be estimated by observing the memory usage of a few PHP-FPM worker processes under load. Magento 2 can be memory-intensive, especially with extensions. Start conservatively and monitor memory usage. If you see `max_children` being reached frequently in your PHP-FPM logs, you may need to increase it, provided you have sufficient RAM.
Opcode Caching
Opcode caching (e.g., OPcache) is non-negotiable for Magento 2 performance. It stores precompiled PHP script bytecode in shared memory, eliminating the need to parse and compile PHP scripts on every request.
OPcache Configuration (`php.ini`)
opcache.enable=1 opcache.enable_cli=1 ; Enable for CLI scripts (e.g., cron jobs) opcache.memory_consumption=128 ; MB. Adjust based on your PHP memory limit and number of scripts. 256MB is often a good starting point for Magento. opcache.interned_strings_buffer=16 ; MB opcache.max_accelerated_files=10000 ; Number of files to cache. Magento has many files. opcache.revalidate_freq=2 ; Check for file updates every 2 seconds. For production, this can be increased to 60 or higher if file changes are infrequent. opcache.validate_timestamps=1 ; Set to 0 in production if you deploy infrequently and want maximum performance, but remember to clear cache manually after deployment. opcache.save_comments=1 opcache.load_comments=1 opcache.enable_file_override=0 opcache.huge_code_pages=1 ; If supported by your OS and hardware, can improve performance. opcache.error_log=/var/log/php/opcache.log opcache.log_level=0 ; Set to 7 for debugging if needed.
`opcache.validate_timestamps`: For production environments where deployments are infrequent, setting `opcache.validate_timestamps=0` and `opcache.revalidate_freq` to a very high value (or disabling it entirely) can yield significant performance gains by eliminating file stat checks. However, this requires a manual cache clear or a full server restart after every deployment. A common compromise is to keep `validate_timestamps=1` and set `revalidate_freq` to something like 60 seconds.
MySQL Tuning for Magento 2 on AWS RDS
Optimizing MySQL, especially when using AWS RDS, involves configuring the database instance itself and tuning its parameters. For Magento 2, key areas include buffer pools, connection limits, and query cache (though the query cache is deprecated and often disabled in modern MySQL versions).
AWS RDS Instance Sizing
Choose an RDS instance type that provides sufficient CPU, RAM, and IOPS for your workload. For Magento 2, `db.r5` or `db.m5` instance families are common. Ensure you select an appropriate storage type (e.g., `gp3` or `io1`) and provision enough IOPS if your workload is I/O bound.
MySQL Parameter Group Tuning
AWS RDS allows you to manage MySQL parameters via Parameter Groups. Create a custom parameter group based on the default for your MySQL version.
Key MySQL Parameters to Tune
# InnoDB Buffer Pool Size # This is the most critical parameter for InnoDB performance. # It caches data and indexes. Aim to set this to 70-80% of the instance's RAM. innodb_buffer_pool_size = 80% of instance RAM # InnoDB Log File Size # Larger log files can improve write performance but increase recovery time. # A common starting point is 512MB or 1024MB. innodb_log_file_size = 1024M # InnoDB Log Buffer Size # Larger buffer can improve write performance for busy systems. innodb_log_buffer_size = 16M # Max Connections # Set this based on your application's needs and available RAM. # Too high can exhaust memory. Too low can cause connection errors. max_connections = 200 ; Adjust based on application and instance RAM # Table Open Cache # Caches file descriptors for open tables. table_open_cache = 2000 ; Adjust based on number of tables and load # Table Definition Cache # Caches table definitions. table_definition_cache = 1024 ; Adjust based on number of tables # Sort Buffer Size # Used for sorting operations. Can be increased for complex queries. sort_buffer_size = 2M # Join Buffer Size # Used for joins that don't use indexes. join_buffer_size = 2M # Read Buffer Size # Used for sequential scans. read_buffer_size = 1M # Readrnd Buffer Size # Used for reading rows after a sort. read_rnd_buffer_size = 2M # Query Cache (Deprecated in MySQL 5.7, removed in 8.0) # If using an older version and it's enabled, tune cautiously. # query_cache_type = 0 # query_cache_size = 0 # Thread Cache Size # Caches threads for reuse. thread_cache_size = 16 ; Adjust based on max_connections # Temporary Tables # Controls the size of temporary tables created in memory. tmp_table_size = 64M max_heap_table_size = 64M # Per-thread buffers (adjust cautiously) # percona_thread_buffers = 1 # Example for Percona Server
`innodb_buffer_pool_size`: This is paramount. On an RDS instance, dedicate a significant portion of RAM to this. For example, on an `db.r5.xlarge` (16 GiB RAM), setting `innodb_buffer_pool_size` to `12G` (12288M) is a reasonable starting point.
`max_connections`: Monitor your application’s actual connection usage. If you see frequent “Too many connections” errors, you may need to increase this, but ensure your instance has enough RAM to support the additional connections. Also, ensure your application’s connection pooling is configured correctly.
Slow Query Log Analysis
Enable and regularly analyze the slow query log to identify and optimize problematic SQL queries. AWS RDS makes this easy by providing access to the slow query log file.
-- Enable slow query logging (in your custom parameter group) slow_query_log = 1 slow_query_log_file = "/rdsdbdata/log/mysql/mysql-slow.log" long_query_time = 2 ; Log queries taking longer than 2 seconds log_queries_not_using_indexes = 1 ; Log queries that don't use indexes
Use tools like `pt-query-digest` (from Percona Toolkit) to analyze the slow query log and identify the most frequent or time-consuming queries. These queries often point to missing indexes, inefficient joins, or poorly written SQL within Magento extensions.
Magento 2 Specific Considerations
Beyond general server tuning, Magento 2 has its own optimization levers:
- Full Page Cache (FPC): Ensure FPM is configured correctly, and consider using Redis or Varnish for FPC. Nginx can also serve static cache entries.
- Database Indexing: Regularly reindex Magento 2 data, especially after product updates or configuration changes. Automate this process using cron jobs.
- Cache Types: Flush Magento’s internal caches (`bin/magento cache:flush`) as needed, but avoid doing so unnecessarily.
- Cron Jobs: Optimize Magento’s cron jobs to run efficiently and at appropriate intervals.
- Extension Audit: Poorly written extensions are a common source of performance bottlenecks. Audit installed extensions and remove or optimize those that are resource-intensive.
- Static Content Deployment: Ensure `bin/magento setup:static-content:deploy` is run for all necessary locales and themes.
By systematically tuning Nginx, PHP-FPM, and MySQL, and by leveraging Magento 2’s built-in optimization features, you can achieve significant performance improvements on AWS. Continuous monitoring and iterative tuning are key to maintaining optimal performance under varying loads.