The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on DigitalOcean for Magento 2
Nginx Configuration for Magento 2 Performance
Optimizing Nginx is crucial for serving static assets efficiently and proxying requests to your Magento backend. We’ll focus on key directives that impact performance and security.
Worker Processes and Connections
The worker_processes directive determines how many worker processes Nginx will spawn. Setting this to auto is generally recommended, allowing Nginx to detect the number of CPU cores. worker_connections defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024, but this can be increased based on your server’s RAM and expected load.
nginx.conf Snippet
worker_processes auto; worker_connections 4096; # Adjust based on RAM and load
Buffering and Caching
Nginx’s buffering directives control how it handles request and response bodies. For Magento, tuning client_body_buffer_size and client_max_body_size is important, especially for uploads. Caching static assets aggressively is paramount. We’ll leverage proxy_cache for dynamic content and browser caching for static files.
Static Asset Caching (Server Block)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
log_not_found off;
}
Proxy Caching (Server Block)
This configuration assumes you have a proxy_cache_path defined in your main nginx.conf. For Magento, it’s often beneficial to cache pages for logged-out users. Be cautious with caching for logged-in users due to personalization.
# Define proxy_cache_path in nginx.conf:
# proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=magento_cache:100m max_size=10g inactive=60m use_temp_path=off;
# In your Magento server block:
proxy_cache magento_cache;
proxy_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
proxy_cache_valid 404 1m; # Cache 404s for 1 minute
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_bypass $http_pragma $http_authorization;
proxy_no_cache $http_pragma $http_authorization;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
# Exclude certain requests from caching (e.g., AJAX, admin, checkout)
location ~* ^/(admin|checkout|rest|V1)/ {
proxy_cache off;
}
location ~* AJAX/ {
proxy_cache off;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
add_header Cache-Control "public, immutable";
}
Gzip Compression
Enabling Gzip compression significantly reduces the size of text-based assets (HTML, CSS, JS), leading to faster load times. Ensure you’re not double-compressing already compressed assets like images.
gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; gzip_disable "msie6"; # Disable for older IE versions
HTTP/2 and TLS Optimization
HTTP/2 offers multiplexing and header compression, improving performance. TLS 1.3 is faster and more secure. Ensure your SSL certificate is valid and your cipher suites are modern.
listen 443 ssl http2; listen [::]:443 ssl http2; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; # Use your preferred DNS resolvers resolver_timeout 5s;
Gunicorn/PHP-FPM Tuning for Magento 2
The application server (Gunicorn for Python-based frameworks, PHP-FPM for PHP) is the bridge between Nginx and your Magento application. Proper tuning here is critical for handling concurrent requests and managing resources.
Gunicorn Configuration (Python/Magento 2)
For a Python-based Magento implementation (less common but possible with frameworks like Django/Flask), Gunicorn’s worker types and counts are key. The sync worker type is the default and simplest, but gevent or event workers can offer better concurrency under I/O-bound loads.
Worker Count Calculation
A common starting point for worker processes is (2 * number_of_cores) + 1. However, for I/O-bound applications like Magento, you might increase this. Monitor CPU and memory usage closely.
Gunicorn Command Line Example
gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application
PHP-FPM Configuration (PHP/Magento 2)
PHP-FPM is the standard for PHP applications. The pm (process manager) setting is crucial. dynamic is often a good balance, allowing FPM to scale workers up and down. ondemand can save memory but might introduce latency on initial requests. static provides predictable performance but can be memory-intensive.
PHP-FPM Pool Configuration (www.conf)
Locate your PHP-FPM pool configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf). Adjust the following directives:
; pm = dynamic ; pm.max_children = 50 ; pm.start_servers = 5 ; pm.min_spare_servers = 2 ; pm.max_spare_servers = 10 ; pm.process_idle_timeout = 10s ; pm.max_requests = 500 ; request_terminate_timeout = 120s ; listen.owner = www-data ; listen.group = www-data ; listen.mode = 0660
Explanation:
pm.max_children: The maximum number of child processes that will be created. This is the most critical setting. Calculate based on available RAM. A common formula is(Total RAM - RAM for OS/Nginx) / Average PHP-FPM Process Size.pm.start_servers: Number of child processes to start when PHP-FPM starts.pm.min_spare_servers: Minimum number of idle supervisor processes.pm.max_spare_servers: Maximum number of idle supervisor processes.pm.max_requests: Maximum number of real requests each child process should execute. Setting this helps prevent memory leaks.request_terminate_timeout: Maximum time a script is allowed to run. Crucial for preventing runaway scripts.
PHP Settings for Magento
Beyond FPM, core PHP settings in php.ini significantly impact Magento performance. Ensure you have sufficient memory limits and execution times.
memory_limit = 512M max_execution_time = 300 max_input_vars = 3000 upload_max_filesize = 64M post_max_size = 64M opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 opcache.revalidate_freq=2 opcache.save_comments=1 opcache.enable_cli=1
MySQL Tuning for Magento 2
MySQL is often the bottleneck in Magento deployments. Aggressive tuning of its configuration is essential. We’ll focus on the mysqld section of my.cnf.
InnoDB Buffer Pool
The innodb_buffer_pool_size is the most critical setting for InnoDB performance. It caches data and indexes. A common recommendation is 70-80% of available RAM on a dedicated database server. On a shared server, adjust accordingly.
[mysqld] innodb_buffer_pool_size = 4G ; Adjust based on available RAM (e.g., 4GB for an 8GB droplet) innodb_buffer_pool_instances = 4 ; Number of buffer pool instances (typically 1 per GB of buffer pool size)
Query Cache (Deprecated/Removed in MySQL 8+)
The MySQL query cache is deprecated and removed in MySQL 8.0. If you are on an older version, it can sometimes help, but it often causes more contention than benefit in high-write environments like Magento. It’s generally recommended to disable it or ensure it’s off.
[mysqld] query_cache_type = 0 query_cache_size = 0
Connection Handling
max_connections should be set high enough to accommodate your application’s needs but not so high that it exhausts server memory. thread_cache_size can improve performance by reusing threads.
[mysqld] max_connections = 200 ; Adjust based on application needs and server RAM thread_cache_size = 16 ; Cache threads for reuse
Logging and I/O
Disabling unnecessary logging in production can improve I/O performance. The slow query log is invaluable for debugging but should be configured carefully.
[mysqld] slow_query_log = 1 slow_query_log_file = /var/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 # For production, consider disabling general log if not actively debugging # general_log = 0 # general_log_file = /var/log/mysql/mysql.log
Other Important Settings
[mysqld] innodb_flush_log_at_trx_commit = 2 ; Trade-off between durability and performance. 2 is faster than 1. innodb_flush_method = O_DIRECT ; Avoid double buffering with OS cache. innodb_log_file_size = 512M ; Larger log files can improve write performance. innodb_io_capacity = 2000 ; Adjust based on disk I/O capabilities. innodb_io_capacity_max = 4000 ; Adjust based on disk I/O capabilities. table_open_cache = 2000 table_definition_cache = 1000
Monitoring and Diagnostics
Tuning is an iterative process. Continuous monitoring is essential to identify bottlenecks and validate your changes. Use a combination of system-level and application-level tools.
System Monitoring
Tools like htop, iotop, vmstat, and netstat provide real-time insights into CPU, memory, disk I/O, and network usage.
# Monitor CPU and Memory htop # Monitor Disk I/O sudo iotop # Monitor System Statistics vmstat 5 # Monitor Network Connections sudo netstat -tulnp | grep -E 'nginx|php-fpm|mysqld'
Nginx Status and Cache
Enable Nginx’s status module to view active connections and cache performance.
# In nginx.conf:
# load_module modules/ngx_http_stub_status_module.so;
# In your server block:
location /nginx_status {
stub_status;
allow 127.0.0.1; # Restrict access
deny all;
}
Access http://yourdomain.com/nginx_status to see metrics like:
Active connections: Current number of active client connections.server accepts handled requests: Total requests handled.reading writing waiting: Breakdown of worker connections.
Check the X-Cache-Status header in your browser’s developer tools to verify proxy cache hits/misses.
PHP-FPM Status
Enable the PHP-FPM status page for insights into process management.
; In your PHP-FPM pool config (e.g., www.conf):
pm.status_path = /fpm_status
; Add to your Nginx server block:
location ~ ^/fpm_status {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust socket path
allow 127.0.0.1;
deny all;
}
Access http://yourdomain.com/fpm_status for metrics like pool, process manager, start time, accepted conn, listen queue, max listen queue, idle processes, active processes, total processes, max active processes, max children reached.
MySQL Slow Query Log Analysis
Regularly analyze the slow query log to identify and optimize inefficient queries. Tools like pt-query-digest from Percona Toolkit are invaluable.
# Install Percona Toolkit (example for Debian/Ubuntu) sudo apt-get update sudo apt-get install percona-toolkit # Analyze the slow query log sudo pt-query-digest /var/log/mysql/mysql-slow.log > /var/log/mysql/mysql-slow-report.txt
Magento Specific Tools
Utilize Magento’s built-in profiling and caching mechanisms. Ensure you’re using Redis for session and cache storage where appropriate.
# Enable Magento Profiler (for development/debugging)
# app/etc/env.php
'profiler' => [
'enabled' => true
],
# Clear Magento Cache
bin/magento cache:clean
bin/magento cache:flush
Conclusion
This playbook provides a robust starting point for tuning Nginx, Gunicorn/PHP-FPM, and MySQL for Magento 2 on DigitalOcean. Remember that every environment is unique. Continuously monitor, test, and iterate on these configurations based on your specific workload and hardware. Prioritize understanding the impact of each setting before applying it to a production environment.