The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on DigitalOcean for WordPress
Nginx Configuration for High-Traffic WordPress
Optimizing Nginx is paramount for serving WordPress efficiently, especially under load. We’ll focus on caching, worker processes, and connection handling. This setup assumes a DigitalOcean Droplet with sufficient RAM and CPU resources.
Worker Processes and Connections
The `worker_processes` directive should ideally be set to the number of CPU cores available. `worker_connections` dictates the maximum number of simultaneous connections a worker process can handle. A good starting point is 1024, but this can be tuned based on observed load.
Nginx `nginx.conf` Snippet
worker_processes auto; # Or set to the number of CPU cores
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 4096; # Increased from default 1024
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off; # Hide Nginx version for security
# ... other http configurations ...
}
Browser and Server-Side Caching
Leveraging Nginx’s ability to cache static assets reduces load on PHP-FPM and Redis. We’ll configure browser caching via `Expires` headers and Nginx’s fastcgi_cache for dynamic content (though this is more advanced and often handled by WordPress plugins, we’ll focus on static assets here).
Static Asset Caching Configuration
http {
# ... other http configurations ...
# Browser caching for static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 30d;
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;
# ... server blocks ...
}
Gunicorn/PHP-FPM Tuning for WordPress
The choice between Gunicorn (for Python-based applications, less common for direct WordPress hosting but relevant if using a headless CMS or API) and PHP-FPM (standard for WordPress) dictates tuning parameters. We’ll cover PHP-FPM as it’s the typical scenario.
PHP-FPM Process Management
PHP-FPM uses a process manager to handle requests. The `pm` directive can be set to `dynamic`, `static`, or `ondemand`. For a busy WordPress site, `dynamic` or `static` are generally preferred. `dynamic` scales based on load, while `static` keeps a fixed number of children running, which can be faster but less memory efficient.
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 parameters:
; Choose a process manager pm = dynamic ; For dynamic PM: ; pm.max_children: Maximum number of children that can be started. ; pm.start_servers: Number of children created at startup. ; pm.min_spare_servers: Minimum number of idle respawned children. ; pm.max_spare_servers: Maximum number of idle respawned children. pm.max_children = 100 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 ; For static PM: ; pm.max_children = 50 ; Keep this number consistent with your server's resources ; Request termination after X requests pm.max_requests = 500 ; Set the user and group user = www-data group = www-data ; Set the listen socket listen = /run/php/php8.1-fpm.sock ; Or use a TCP port like 127.0.0.1:9000 listen.owner = www-data listen.group = www-data listen.mode = 0660 ; Other useful settings request_terminate_timeout = 60s ; pm.process_idle_timeout = 10s ; For ondemand PM ;
Tuning `pm.max_children`: This is critical. A common formula is (Total RAM - RAM for OS/other services) / Average PHP process size. Monitor memory usage with htop or free -m. If you see excessive swapping, reduce this value. If your server is consistently underutilized and requests are queued, you might increase it.
PHP Opcode Caching
Opcode caching (like OPcache) is essential for PHP performance. It stores precompiled script bytecode in shared memory, avoiding the need to parse PHP files on every request.
OPcache Configuration (`php.ini`)
Edit your main php.ini file (e.g., /etc/php/8.1/fpm/php.ini) and ensure these settings are present and tuned:
[OPcache] opcache.enable=1 opcache.enable_cli=1 ; Enable for CLI scripts too opcache.memory_consumption=128 ; MB, adjust based on your site's complexity and traffic opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 ; Increase if you have many PHP files opcache.revalidate_freq=60 ; Check for file updates every 60 seconds (0 to disable, 1 to check every script run) opcache.validate_timestamps=1 ; Set to 0 in production if you manually clear cache after deploys opcache.save_comments=1 opcache.load_comments=1 opcache.enable_file_override=0 opcache.fast_shutdown=0 opcache.optimization_level=0xFFFFFFFF ; Default optimization level
After modifying PHP-FPM or PHP settings, remember to restart the services:
sudo systemctl restart php8.1-fpm sudo systemctl restart nginx
Redis for Object Caching and Session Management
Redis is an invaluable tool for WordPress performance, primarily for object caching. It stores frequently accessed database query results and other computed data in memory, drastically reducing database load.
Redis Configuration (`redis.conf`)
The primary configuration file is typically /etc/redis/redis.conf. Key parameters to tune include:
# Bind to localhost to prevent external access unless necessary bind 127.0.0.1 -::1 # Port to listen on port 6379 # Set a strong password for security # requirepass your_very_strong_password # Max memory to use. Adjust based on available RAM. # Example: 512MB maxmemory 512mb # Policy for evicting keys when maxmemory is reached. # 'allkeys-lru' is a good default for object caching. maxmemory-policy allkeys-lru # Persistence settings (optional, depending on use case) # For object caching, disabling persistence is often fine. # If used for session storage or critical data, configure RDB or AOF. save "" # Disable RDB snapshots if not needed appendonly no # Disable AOF if not needed # TCP keepalive settings tcp-keepalive 300 # Logging loglevel notice logfile /var/log/redis/redis-server.log
`maxmemory` and `maxmemory-policy`: These are crucial. Set `maxmemory` to a value that leaves ample RAM for your OS, Nginx, and PHP-FPM. `allkeys-lru` (Least Recently Used) is excellent for WordPress object caching as it discards the least recently accessed items when memory is full.
Integrating Redis with WordPress
To use Redis for object caching, you’ll need a WordPress plugin. The most popular and well-maintained is “Redis Object Cache” by Till Krüss. After installing and activating the plugin, you’ll need to configure it to connect to your Redis instance.
WordPress `wp-config.php` Snippet
The Redis Object Cache plugin typically handles the connection details via its admin interface. However, for direct configuration or if using other Redis plugins, you might add constants to your wp-config.php:
define('WP_REDIS_CLIENT', 'phpredis'); // Or 'credis'
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', ''); // Set if you configured requirepass
define('WP_REDIS_TIMEOUT', 1);
define('WP_REDIS_READ_TIMEOUT', 1);
define('WP_REDIS_DATABASE', 0); // Default database index
After configuring Redis and the WordPress plugin, restart the Redis service:
sudo systemctl restart redis-server
Monitoring and Diagnostics
Continuous monitoring is key to identifying bottlenecks and validating tuning efforts. Use a combination of system tools and application-level metrics.
System-Level Monitoring
- `htop` / `top`: Monitor CPU and RAM usage. Look for high CPU load from Nginx worker processes or PHP-FPM, and check for excessive memory consumption or swapping.
- `free -m`: Provides a clear overview of memory usage, including available RAM and swap.
- `netstat -tulnp`: Shows listening ports and associated processes. Verify Nginx and Redis are running and accessible.
- `iostat`: Monitor disk I/O, crucial if your database or file system is a bottleneck.
Nginx Status
Enable the Nginx status module to get insights into active connections and requests.
Nginx `status.conf` Snippet
# Add this within your http block in nginx.conf
server {
listen 8080; # Or another port, accessible only from localhost
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1; # Restrict access
deny all;
}
}
Access http://your_server_ip:8080/nginx_status to see output like:
Active connections: 1234 server accepts handled requests 1234567 1234567 1234567 Reading: 10 Writing: 5 Waiting: 1099
High `Waiting` connections might indicate PHP-FPM is overloaded or slow to respond. High `Active connections` is expected under load.
PHP-FPM Status
Enable the PHP-FPM status page for process monitoring.
PHP-FPM `status.conf` Snippet
; In your PHP-FPM pool config (e.g., www.conf)
; Ensure pm is set to dynamic or static
pm.status_path = /status
; Add this to your Nginx configuration to expose the status page
location ~ ^/status$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Match your listen socket
allow 127.0.0.1;
deny all;
}
Access http://your_server_ip/status. You’ll see output detailing active processes, idle processes, and requests.
Redis Monitoring
Use the Redis CLI for real-time insights.
redis-cli 127.0.0.1:6379> INFO memory # Memory used_memory:123456789 used_memory_human:117.75M ... maxmemory:536870912 maxmemory_human:512.00M maxmemory_policy:allkeys-lru ... 127.0.0.1:6379> INFO stats # Stats total_connections_received:1234567 ... evicted_keys:12345 ...
Monitor `used_memory` to ensure you’re within `maxmemory` limits and check `evicted_keys` to understand cache churn.