The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on AWS for WooCommerce
Nginx as a High-Performance Frontend for WooCommerce
When deploying WooCommerce on AWS, Nginx serves as the de facto standard for a high-performance web server and reverse proxy. Its event-driven, asynchronous architecture excels at handling a large number of concurrent connections, making it ideal for the often-bursty traffic patterns of e-commerce sites. The key to unlocking its full potential lies in meticulous configuration, particularly around worker processes, connection limits, and caching.
Nginx Worker Processes and Connections
The `worker_processes` directive dictates how many worker processes Nginx will spawn. Setting this to `auto` is generally recommended, allowing Nginx to dynamically adjust based on the number of CPU cores available. This ensures optimal utilization of your EC2 instance’s processing power. The `worker_connections` directive, on the other hand, defines the maximum number of simultaneous connections that each worker process can handle. This value, combined with `worker_processes`, determines the total connection capacity. A common starting point is 1024, but this should be tuned based on your instance type and expected load.
Crucially, the operating system’s file descriptor limit must be high enough to accommodate Nginx’s needs. Each connection consumes a file descriptor. You can check the current limit with ulimit -n and increase it by editing /etc/security/limits.conf.
Nginx Configuration Snippet
Here’s a foundational Nginx configuration snippet for a WooCommerce deployment. This assumes you’re using Gunicorn (for Python-based backends) or PHP-FPM. Adjust `worker_processes` and `worker_connections` based on your EC2 instance’s CPU cores and expected load.
Global Nginx Settings
Place these directives in your main nginx.conf file, typically in the http block.
worker_processes auto;
# Adjust worker_connections based on your instance type and expected load.
# A common starting point is 1024. Ensure OS limits are also increased.
worker_connections 4096;
multi_accept on;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
# This should be at least worker_connections
worker_connections 4096;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression for static and dynamic content
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
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;
# Buffering settings for proxying
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Client body buffer size - adjust if you have large uploads
client_max_body_size 100M;
client_body_buffer_size 100M;
# Access log configuration
access_log /var/log/nginx/access.log;
# Include server blocks
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
WooCommerce Server Block (Example)
This configuration assumes your WooCommerce application is served by Gunicorn (Python) or PHP-FPM. Adapt the proxy_pass directive accordingly.
server {
listen 80;
server_name your_domain.com www.your_domain.com;
root /var/www/your_woocommerce_app/public; # Adjust to your app's public directory
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
# For PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# Adjust the socket path to your PHP-FPM pool
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Example for PHP 7.4
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# For Gunicorn (Python)
# location / {
# proxy_pass http://unix:/path/to/your/gunicorn.sock; # Or http://127.0.0.1:8000;
# 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_set_header X-Forwarded-Proto $scheme;
# }
# Deny access to sensitive files
location ~ /\.ht {
deny all;
}
# Caching static assets
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# Prevent access to wp-config.php and other sensitive files
location ~* (wp-config\.php|readme\.html|license\.txt) {
deny all;
}
# Serve static files directly
location ~ ^/(images|javascript|js|css|flash|media|static)/ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
Gunicorn/PHP-FPM Tuning for WooCommerce
The application server (Gunicorn for Python, PHP-FPM for PHP) is the engine that processes your WooCommerce requests. Optimizing its configuration is paramount for responsiveness and scalability.
Gunicorn Configuration
Gunicorn’s performance is largely dictated by its worker class and the number of worker processes. For I/O-bound applications like WooCommerce, the gevent worker class is often preferred due to its ability to handle many concurrent connections efficiently. The number of workers is typically set to (2 * number_of_cores) + 1 as a starting point.
Consider using a Unix socket for communication between Nginx and Gunicorn if they are on the same host. This can offer a slight performance improvement over TCP/IP sockets.
Gunicorn Command Line Example
gunicorn --workers 4 --worker-class gevent --bind unix:/path/to/your/gunicorn.sock your_app.wsgi:application --log-level info --access-logfile /var/log/gunicorn/access.log --error-logfile /var/log/gunicorn/error.log
Explanation:
--workers 4: Sets the number of worker processes. Adjust based on your CPU cores.--worker-class gevent: Uses the gevent worker class for asynchronous I/O.--bind unix:/path/to/your/gunicorn.sock: Binds Gunicorn to a Unix socket. Alternatively, use--bind 127.0.0.1:8000for a TCP socket.your_app.wsgi:application: Points to your application’s WSGI entry point.
PHP-FPM Configuration
PHP-FPM (FastCGI Process Manager) is the standard for serving PHP applications. Its performance tuning involves managing the number of child processes and their lifecycle. The pm (process manager) setting is crucial. For production, pm = dynamic or pm = ondemand are generally recommended over pm = static, as they allow PHP-FPM to scale its worker pool based on demand, preventing resource exhaustion.
Key directives to tune within your PHP-FPM pool configuration (e.g., /etc/php/7.4/fpm/pool.d/www.conf):
PHP-FPM Pool Configuration Example
; For a dynamic process manager pm = dynamic ; Maximum number of children that can be started. pm.max_children = 100 ; Maximum number of children that can be started at one time. pm.start_servers = 10 ; Minimum number of children that should always be running. pm.min_spare_servers = 5 ; Maximum number of children that can stay idle in the meantime. pm.max_spare_servers = 20 ; The number of requests each child process should execute before respawning. ; This helps to prevent memory leaks. pm.max_requests = 500 ; If using 'ondemand' process manager ; pm = ondemand ; pm.max_children = 50 ; pm.process_idle_timeout = 10s ; pm.max_requests = 500 ; Adjust listen socket to match Nginx configuration listen = /var/run/php/php7.4-fpm.sock ; listen.owner = www-data ; listen.group = www-data ; listen.mode = 0660 ; Adjust user and group to match your web server user user = www-data group = www-data ; Adjust request termination timeout request_terminate_timeout = 120s ; Adjust idle timeout ; pm.idle_timeout = 10s ; Adjust slowlog for debugging ; slowlog = /var/log/php/php7.4-fpm.slow.log
Tuning Notes:
pm.max_children: This is the most critical setting. It should be high enough to handle peak load but not so high that it exhausts server memory. Monitor your server’s memory usage and adjust accordingly.pm.start_servers,pm.min_spare_servers,pm.max_spare_servers: These control how PHP-FPM manages its worker pool. Dynamic settings are generally preferred for fluctuating loads.pm.max_requests: Essential for preventing memory leaks in long-running PHP processes. A value between 100 and 500 is typical.request_terminate_timeout: Set this to a reasonable value to prevent long-running scripts from holding up workers indefinitely.
MongoDB Performance Tuning for WooCommerce
While WooCommerce primarily relies on MySQL, MongoDB can be used for caching, session storage, or specific plugins. Optimizing MongoDB is crucial for these use cases.
MongoDB WiredTiger Storage Engine
The WiredTiger storage engine is the default and recommended engine for MongoDB. Its performance is heavily influenced by its cache size. The WiredTiger cache aims to hold the working set of your data and indexes in RAM for fast access.
WiredTiger Cache Configuration
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 0.75 # Allocate 75% of available RAM, or a fixed value like 4GB
collectionConfig:
cacheResource:
- block:
size: 4KB
indexConfig:
prefixCompression: true
cacheResource:
- block:
size: 256B
Tuning Notes:
cacheSizeGB: This is the most critical parameter. A common recommendation is to allocate 50-75% of the system’s RAM to the WiredTiger cache. However, ensure enough RAM is left for the OS and other processes. For a dedicated MongoDB instance, you might allocate more. For a shared instance, be more conservative. Monitordb.serverStatus().wiredTiger.cachefor cache hit rates. Aim for a high hit rate (e.g., > 90%).indexConfig.prefixCompression: Enabling prefix compression for indexes can significantly reduce their size, improving cache efficiency and read performance.
MongoDB Query Optimization and Indexing
Inefficient queries and missing indexes are common performance bottlenecks. Regularly analyze your MongoDB slow query log to identify problematic queries.
Identifying Slow Queries
Ensure slow query logging is enabled in your mongod.conf:
# mongod.conf
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
verbosity: 0
quiet: false
slowms: 100 # Log queries slower than 100ms
traceSampleRates:
query: 1 # Sample 1% of all queries
command: 1 # Sample 1% of all commands
Use the db.collection.find().explain("executionStats") method in the MongoDB shell to analyze query performance. Look for high totalKeysExamined relative to totalDocsExamined, and a high totalKeysExamined overall.
Creating Effective Indexes
Based on your slow query analysis, create appropriate indexes. For example, if you frequently query a `products` collection by `category` and `status`, an index like this would be beneficial:
db.products.createIndex( { category: 1, status: 1 } )
Consider compound indexes, covering indexes, and TTL indexes where appropriate. Regularly review and optimize your indexes using tools like Percona Monitoring and Management (PMM) or MongoDB Atlas’s performance tools.
AWS Infrastructure Considerations
The underlying AWS infrastructure plays a significant role. Choosing the right EC2 instance types, EBS volume types, and network configurations is crucial.
EC2 Instance Selection
For compute-intensive workloads like WooCommerce, consider EC2 instances with good CPU performance (e.g., C-series). For memory-intensive caching layers (e.g., Redis, or if MongoDB is heavily used), R-series instances are suitable. Network-optimized instances (e.g., N-series) can be beneficial for high-traffic sites with significant data transfer.
EBS Volume Optimization
If MongoDB is hosted on EC2, use gp3 or io2 EBS volumes for production workloads. gp3 offers a good balance of price and performance, allowing you to provision IOPS and throughput independently. io2 provides higher IOPS and durability for demanding workloads. Avoid gp2 for production due to its burst-based performance model.
Load Balancing and Auto Scaling
Utilize AWS Elastic Load Balancing (ELB) to distribute traffic across multiple EC2 instances running your WooCommerce application. Configure Auto Scaling Groups to automatically adjust the number of instances based on demand, ensuring high availability and cost-efficiency. Ensure your ELB health checks are configured correctly to monitor the responsiveness of your application servers.