The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and DynamoDB on OVH for WordPress
Nginx Configuration for High-Traffic WordPress on OVH
Optimizing Nginx is paramount for serving high-traffic WordPress sites. This section details critical Nginx directives and configurations tailored for an OVH environment, focusing on performance, security, and efficient resource utilization. We’ll assume a typical setup with PHP-FPM for WordPress execution.
Worker Processes and Connections
The `worker_processes` directive dictates how many worker processes Nginx will spawn. A common recommendation is to set it to the number of CPU cores available. `worker_connections` defines the maximum number of simultaneous connections that each worker process can handle. The total theoretical maximum connections is `worker_processes * worker_connections`.
On an OVH VPS or dedicated server, you can determine the CPU core count using `nproc` or by inspecting /proc/cpuinfo. For a 4-core server, a good starting point is:
worker_processes 4; # Or auto; to let Nginx decide based on CPU cores
events {
worker_connections 4096; # Adjust based on expected load and available RAM
multi_accept on;
}
multi_accept on; allows workers to accept multiple connections at once, improving efficiency under heavy load.
Buffering and Caching
Nginx’s buffering directives control how it handles request and response bodies. Properly tuned buffers can reduce disk I/O and improve throughput. For static assets, aggressive caching is essential.
client_body_buffer_size and client_header_buffer_size should be set appropriately. For most WordPress sites, default values are often sufficient, but for large file uploads, you might need to increase client_body_buffer_size. The proxy_buffers and proxy_buffer_size directives are crucial when proxying to PHP-FPM.
http {
# ... other http directives ...
client_body_buffer_size 10m; # For larger uploads
client_header_buffer_size 1m;
proxy_buffers 8 16k; # Adjust number and size based on typical response sizes
proxy_buffer_size 32k;
# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, no-transform";
access_log off;
log_not_found off;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
}
Keepalive Connections
Enabling keepalive connections reduces the overhead of establishing new TCP connections for each request. This is particularly beneficial for clients making multiple requests to your WordPress site.
http {
# ... other http directives ...
keepalive_timeout 65; # Default is 75. Adjust based on server load and client behavior.
keepalive_requests 1000; # Max requests per keepalive connection.
}
Tuning PHP-FPM for WordPress
PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications like WordPress. Its configuration directly impacts WordPress performance. We’ll focus on the www.conf file, typically located at /etc/php/[version]/fpm/pool.d/www.conf.
Process Management (pm)
PHP-FPM offers several process management strategies: static, dynamic, and ondemand. For a busy WordPress site, dynamic or static are usually preferred over ondemand, which can introduce latency on initial requests.
Dynamic:
pm = dynamic pm.max_children = 50 ; Max number of children at any one time pm.start_servers = 5 ; Number of children when FPM starts pm.min_spare_servers = 5 ; Min number of idle/spare children pm.max_spare_servers = 10 ; Max number of idle/spare children pm.max_requests = 500 ; Max requests a child process should execute before respawning
Static:
pm = static pm.max_children = 50 ; Fixed number of children
The optimal values for these directives depend heavily on your server’s RAM and the number of concurrent requests. Start with conservative values and monitor memory usage. A common approach is to set pm.max_children based on available RAM: (Total RAM - RAM for OS/Nginx/DB) / Average PHP Process Memory Usage. Average PHP process memory can be estimated by observing ps aux | grep php-fpm after a few requests.
Request Termination
pm.max_requests is crucial for preventing memory leaks in long-running PHP processes. A value between 250 and 1000 is typical. For very high-traffic sites, a lower value might be necessary to ensure stability.
Slowlog
Enabling the slowlog is invaluable for identifying slow PHP scripts that might be impacting WordPress performance. Configure it to log requests exceeding a certain execution time.
request_slowlog_timeout = 10 ; Log requests that take longer than 10 seconds slowlog = /var/log/php/php-fpm.slow.log
Ensure the log directory exists and has correct permissions: sudo mkdir -p /var/log/php && sudo chown www-data:www-data /var/log/php (adjust user/group if necessary).
DynamoDB for WordPress: Caching and Session Management
While WordPress is traditionally database-centric, offloading certain operations to a NoSQL store like DynamoDB can significantly improve performance, especially for caching and session management. This requires a WordPress plugin that supports DynamoDB integration. For this example, we’ll assume a plugin is in place and focus on the AWS SDK configuration and best practices.
AWS SDK Configuration (PHP)
The AWS SDK for PHP needs to be installed (typically via Composer) and configured with appropriate credentials and region. For security, use IAM roles if running on EC2, or IAM users with restricted policies if running elsewhere.
// Example using environment variables for credentials and region
// Ensure AWS SDK is installed: composer require aws/aws-sdk-php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
$config = [
'region' => getenv('AWS_REGION') ?: 'us-east-1', // e.g., 'eu-west-3' for Paris
'version' => 'latest',
// Credentials can be automatically discovered from environment variables,
// shared credential files, or IAM roles.
// Explicitly setting them is also possible but less recommended for security.
// 'credentials' => [
// 'key' => 'YOUR_AWS_ACCESS_KEY_ID',
// 'secret' => 'YOUR_AWS_SECRET_ACCESS_KEY',
// ],
];
try {
$dynamoDbClient = new DynamoDbClient($config);
$marshaler = new Marshaler(); // Useful for converting PHP types to DynamoDB types
// Example: Check if a table exists (replace 'YourWordPressCacheTable' with your table name)
$tableName = 'YourWordPressCacheTable';
$result = $dynamoDbClient->describeTable(['TableName' => $tableName]);
echo "Table {$tableName} exists.\n";
} catch (\Aws\Exception\AwsException $e) {
// Handle exceptions, e.g., table not found, credentials error
error_log("DynamoDB Error: " . $e->getMessage());
// Depending on your plugin, you might fall back to a different cache or
// disable DynamoDB caching for this request.
}
DynamoDB Table Design for Caching
A common pattern for WordPress object caching in DynamoDB involves a single table with a primary key that serves as the cache key. The value can be stored as a blob or serialized string.
Table Name: WordPressCache
Primary Key:
- Partition Key:
cache_key(String) – This will be the WordPress cache key (e.g.,alloptions,post_123_content).
Attributes:
cache_value(String/Binary) – The serialized WordPress object or data. Use Binary if storing raw data.ttl(Number) – Optional: Unix timestamp for Time-To-Live expiration.
When using TTL, ensure the DynamoDB TTL feature is enabled on the table and the ttl attribute is correctly populated by your plugin.
Optimizing DynamoDB Operations
For caching, you’ll primarily use PutItem (to set cache) and GetItem (to retrieve cache). Consider the following:
- Provisioned Throughput: Monitor read/write capacity units (RCUs/WCUs) and adjust them based on traffic. Use Auto Scaling if available.
- Batch Operations: If your plugin supports it, use
BatchWriteItemfor multiple writes andBatchGetItemfor multiple reads to reduce the number of API calls. - Consistent Reads: For caching, eventually consistent reads are usually sufficient and cheaper (half the RCU cost) than strongly consistent reads.
- Error Handling: Implement robust error handling for DynamoDB operations. If a cache operation fails, the plugin should gracefully fall back to fetching from the primary database.
// Example: Storing an item
$cacheKey = 'my_custom_cache_key';
$cacheData = serialize(['data' => 'some_value', 'timestamp' => time()]);
$ttl = time() + 3600; // Expires in 1 hour
try {
$dynamoDbClient->putItem([
'TableName' => $tableName,
'Item' => $marshaler->marshalItem([
'cache_key' => $cacheKey,
'cache_value' => $cacheData,
'ttl' => $ttl,
]),
// Use ConsistentRead => true for strongly consistent reads if required
// 'ConsistentRead' => false,
]);
} catch (\Aws\Exception\AwsException $e) {
error_log("DynamoDB PutItem Error: " . $e->getMessage());
}
// Example: Retrieving an item
try {
$result = $dynamoDbClient->getItem([
'TableName' => $tableName,
'Key' => $marshaler->marshalItem(['cache_key' => $cacheKey]),
// 'ConsistentRead' => false, // Default is false (eventually consistent)
]);
if (isset($result['Item'])) {
$item = $marshaler->unmarshalItem($result['Item']);
// Check TTL if not using DynamoDB's TTL feature
if (isset($item['ttl']) && $item['ttl'] < time()) {
// Cache expired, delete it
$dynamoDbClient->deleteItem([
'TableName' => $tableName,
'Key' => $marshaler->marshalItem(['cache_key' => $cacheKey]),
]);
echo "Cache expired and deleted.\n";
} else {
$retrievedData = unserialize($item['cache_value']);
echo "Cache hit: " . print_r($retrievedData, true) . "\n";
}
} else {
echo "Cache miss.\n";
}
} catch (\Aws\Exception\AwsException $e) {
error_log("DynamoDB GetItem Error: " . $e->getMessage());
}
Monitoring and Diagnostics
Continuous monitoring is key to maintaining optimal performance. Utilize a combination of system-level tools and application-specific metrics.
Nginx Monitoring
Use Nginx’s stub_status module to get real-time connection and request statistics. Ensure it’s enabled in your Nginx build.
http {
# ... other http directives ...
server {
listen 80;
server_name your_domain.com;
location /nginx_status {
stub_status;
allow 127.0.0.1; # Restrict access to localhost
deny all;
}
# ... other locations ...
}
}
Access http://your_domain.com/nginx_status to see output like:
Active connections: 1234 server accepts handled requests 1234567 1234567 1234567 Reading: 5 Writing: 10 Waiting: 1000
Key metrics to watch:
- Active connections: High numbers indicate sustained load.
acceptsvs.handled: A large difference might indicate issues with worker connections or OS limits.requests: Total requests served.Reading,Writing,Waiting: These indicate the state of connections. HighWaitingcan mean workers are busy or there are too few workers.
PHP-FPM Monitoring
PHP-FPM’s status page provides insights into pool performance. Enable it similarly to Nginx’s status page.
location ~ ^/fpm_status {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust path to your PHP-FPM socket
allow 127.0.0.1;
deny all;
}
Accessing http://your_domain.com/fpm_status?full will show detailed pool statistics, including:
pool: Pool name.process manager: e.g., dynamic.start since: When the pool started.accepted conn: Total connections accepted.listen queue: Number of requests in the queue waiting for a process. High values indicate FPM is overloaded.max listen queue: Maximum queue length.active processes: Number of processes currently handling requests.idle processes: Number of idle processes.max children reached: Number of times the maximum number of children was reached. High values suggestpm.max_childrenneeds increasing or requests are too long.
DynamoDB Monitoring
AWS CloudWatch is the primary tool for monitoring DynamoDB. Key metrics include:
- ConsumedReadCapacityUnits / ConsumedWriteCapacityUnits: Track actual usage against provisioned capacity.
- ThrottledRequests: Indicates you’re exceeding provisioned capacity.
- SystemErrors: Monitor for any underlying AWS issues.
- Latency: Track average and maximum latency for
GetItem,PutItem, etc.
Set up CloudWatch Alarms for critical metrics like throttled requests or high latency to proactively address issues.
Security Considerations
While performance is key, security cannot be overlooked. Ensure your configurations adhere to best practices.
- Nginx: Disable unnecessary modules, use TLS/SSL, rate-limit suspicious IPs, and protect sensitive directories (e.g.,
wp-admin,wp-includes) with basic authentication or IP whitelisting. - PHP-FPM: Run PHP-FPM as a non-privileged user (e.g.,
www-data). Restrict access to sensitive PHP files. - DynamoDB: Use IAM roles/policies with the principle of least privilege. Never embed AWS credentials directly in code. Encrypt data at rest and in transit.