The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis 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 OVH’s infrastructure, focusing on caching, connection management, and security.
Worker Processes and Connections
The worker_processes directive dictates how many worker processes Nginx will spawn. A common best practice is to set it to the number of CPU cores available. For OVH instances, you can determine this using nproc or lscpu.
worker_connections defines the maximum number of simultaneous connections that each worker process can handle. The total maximum connections will be worker_processes * worker_connections. Ensure your system’s file descriptor limits (ulimit -n) are set high enough to accommodate this.
Caching Strategies
Leveraging Nginx’s FastCGI cache for PHP-FPM output significantly reduces server load. This involves defining cache zones and instructing Nginx to cache responses from the backend.
FastCGI Cache Setup
First, define a cache zone in your http block. The keys_zone name is arbitrary, and max_size should be set based on available disk space. inactive specifies how long items remain in the cache if not accessed.
http {
# ... other http directives ...
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wp_cache:100m inactive=60m max_size=10g use_temp_path=off;
fastcgi_temp_path /var/tmp/nginx/fastcgi_temp; # Ensure this directory exists and is writable by nginx user
# ... other http directives ...
}
Next, configure your WordPress site’s server block to utilize this cache. We’ll cache based on the request URI and query string, and bypass the cache for logged-in users or specific POST requests.
server {
listen 80;
server_name your-domain.com;
root /var/www/your-wordpress-site;
index index.php index.html index.htm;
# ... other server directives ...
# FastCGI Cache Configuration
set $skip_cache 0;
# Don't cache logged in users or admin pages
if ($http_cookie ~* "wordpress_logged_in_|comment_author_|wp-postpass") {
set $skip_cache 1;
}
if ($request_method = POST) {
set $skip_cache 1;
}
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|/feed/|index.php?s=") {
set $skip_cache 1;
}
# Cache key based on request URI and query string
set $cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust to your PHP-FPM version and socket path
# Cache directives
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache wp_cache;
fastcgi_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
fastcgi_cache_valid 404 1m; # Cache 404s for 1 minute
fastcgi_cache_key $cache_key;
fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
}
# Serve static files directly
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# Deny access to sensitive files
location ~ /\.ht {
deny all;
}
}
# Ensure the cache directory exists and has correct permissions
# sudo mkdir -p /var/cache/nginx/wordpress
# sudo chown www-data:www-data /var/cache/nginx/wordpress
# sudo mkdir -p /var/tmp/nginx/fastcgi_temp
# sudo chown www-data:www-data /var/tmp/nginx/fastcgi_temp
Gzip Compression and Browser Caching
Enable Gzip compression to reduce the size of text-based assets. Configure browser caching for static assets to improve repeat visit performance.
http {
# ... other http directives ...
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;
# Browser caching for static assets (already shown in server block, but can be global)
# location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
# expires 30d;
# add_header Cache-Control "public, no-transform";
# }
# ... other http directives ...
}
Security Headers and Rate Limiting
Implement essential security headers and consider rate limiting to protect against brute-force attacks and excessive requests.
server {
# ... other server directives ...
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always; # CSP is complex, tailor carefully
# Rate Limiting (example: limit requests to /wp-login.php)
location = /wp-login.php {
limit_req zone=login_limit burst=5 nodelay;
try_files $uri $uri/ /index.php?$args;
}
# Define the rate limit zone
# http {
# ...
# limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m; # 5 requests per minute per IP
# ...
# }
# ... other server directives ...
}
Gunicorn/PHP-FPM Tuning for WordPress
The application server (Gunicorn for Python apps, PHP-FPM for PHP) is the next critical layer. Proper tuning here directly impacts request processing speed and resource utilization.
PHP-FPM Configuration
PHP-FPM (FastCGI Process Manager) is the standard for running PHP applications. The key configuration file is typically php-fpm.conf or files within php-fpm.d/ (e.g., www.conf).
Process Management (pm)
PHP-FPM offers three process management modes: static, dynamic, and ondemand. For WordPress, dynamic is often a good balance.
static: Spawns a fixed number of child processes. Good for predictable loads but can waste resources if idle.dynamic: Spawns processes as needed, up to a defined maximum. It can also kill idle processes. This is generally recommended.ondemand: Spawns processes only when a request arrives and kills them after a timeout. Saves resources but can introduce latency on the first request.
Here’s an example www.conf snippet for dynamic mode. Adjust pm.max_children based on your server’s RAM and the typical memory footprint of your WordPress site’s requests. A common starting point is (Total RAM – Nginx RAM – OS RAM) / Average PHP Request Memory. Monitor memory usage closely.
; /etc/php/7.4/fpm/pool.d/www.conf (example path) [www] user = www-data group = www-data listen = /var/run/php/php7.4-fpm.sock ; Match this with your Nginx config listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 100 ; Maximum number of children that can be started. pm.start_servers = 5 ; Number of children created at startup. pm.min_spare_servers = 10 ; Minimum number of idle tans. pm.max_spare_servers = 20 ; Maximum number of idle tans. pm.process_idle_timeout = 10s ; The timeout for killing idle processes. pm.max_requests = 500 ; Maximum number of requests each child process should serve. ; Other important settings request_terminate_timeout = 60s ; Timeout for script execution ; rlimit_files = 4096 ; rlimit_nofile = 4096 ; catch_workers_output = yes ; Useful for debugging, but can impact performance
After modifying PHP-FPM configuration, reload the service:
sudo systemctl reload php7.4-fpm
Gunicorn Configuration (if applicable)
If your WordPress site is served via a Python framework (e.g., Django, Flask) using Gunicorn, tuning is different. Gunicorn’s worker types and counts are key.
Worker Types and Count
Gunicorn supports several worker types:
sync(Synchronous): The default. Each worker handles one request at a time. Simple but can block.eventlet,gevent: Asynchronous workers using green threads. Can handle many concurrent connections efficiently.tornado: Uses Tornado’s asynchronous I/O loop.
For WordPress, which is typically I/O bound (database, file system), asynchronous workers like gevent or eventlet can be beneficial if your application logic allows for it. However, if your WordPress setup is primarily PHP-FPM, this section is not directly applicable.
The number of workers is typically calculated as (2 * Number of CPU Cores) + 1. However, for I/O-bound applications, you might increase this. Monitor CPU and memory usage.
# Example Gunicorn command with gevent workers # Adjust workers based on CPU cores and application I/O gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 your_wsgi_app:application
Ensure your Nginx configuration correctly proxies requests to Gunicorn’s bind address and port.
server {
# ...
location / {
proxy_pass http://127.0.0.1:8000; # Assuming Gunicorn is on localhost: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;
}
# ...
}
Redis Optimization for WordPress Caching
Redis is an excellent in-memory data structure store often used for object caching in WordPress, significantly reducing database load. Proper Redis configuration is crucial for performance.
Redis Configuration (`redis.conf`)
The primary configuration file is usually /etc/redis/redis.conf. Key parameters to tune include memory management, persistence, and network settings.
Memory Management
maxmemory: Sets the maximum memory Redis can use. This is critical to prevent Redis from consuming all available RAM and causing system instability. Set this to a value that leaves ample room for the OS and other services (like Nginx workers and PHP-FPM children).
# Example: Limit Redis to 2GB of RAM maxmemory 2gb
maxmemory-policy: Defines how Redis evicts keys when maxmemory is reached. For WordPress object caching, allkeys-lru (Least Recently Used) is a common and effective choice.
# Evict keys using LRU algorithm when max memory is reached maxmemory-policy allkeys-lru
Persistence
For WordPress object caching, persistence (saving data to disk) is often unnecessary and can introduce overhead. Disabling it can improve performance.
# Disable RDB persistence save "" # Disable AOF persistence appendonly no
If you *do* require persistence for other use cases, ensure appendfsync is tuned appropriately (e.g., appendfsync everysec for a balance between durability and performance).
Network and Security
Bind Redis to a specific IP address (e.g., localhost or a private network interface) to restrict access. Use a strong password with requirepass.
# Bind to localhost to only allow local connections bind 127.0.0.1 # Set a strong password requirepass YourVeryStrongPasswordHere
After modifying redis.conf, restart the Redis service:
sudo systemctl restart redis-server
WordPress Redis Integration
To use Redis for WordPress object caching, you’ll need a plugin like “Redis Object Cache” or “W3 Total Cache” (with Redis enabled). Ensure the plugin is configured with the correct Redis host, port, and password.
// Example configuration in wp-config.php for Redis Object Cache plugin
define('WP_REDIS_CLIENT', 'phpredis'); // Or 'credis' if phpredis extension is not installed
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', 'YourVeryStrongPasswordHere');
define('WP_REDIS_TIMEOUT', 1);
define('WP_REDIS_READ_TIMEOUT', 1);
define('WP_REDIS_DATABASE', 0); // Default database
Verify the connection through the WordPress admin interface (usually under Settings -> Redis) or by using redis-cli:
redis-cli 127.0.0.1:6379> AUTH YourVeryStrongPasswordHere OK 127.0.0.1:6379> PING PONG 127.0.0.1:6379> INFO memory # Memory used_memory:12345678 # Should increase as WordPress uses cache used_memory_human:11.78M ...
Monitoring and Diagnostics
Continuous monitoring is essential to identify bottlenecks and ensure optimal performance. Utilize system monitoring tools and application-specific logs.
System Monitoring
Tools like htop, iotop, vmstat, and netstat are invaluable for real-time system performance analysis.
# Monitor CPU, Memory, Load htop # Monitor I/O activity sudo iotop # Monitor system statistics (memory, swap, I/O, CPU) vmstat 5 # Check network connections and listening ports sudo netstat -tulnp | grep -E 'nginx|php-fpm|redis'
Log Analysis
Regularly review Nginx access and error logs, PHP-FPM logs, and Redis logs. Tools like GoAccess or ELK stack can help aggregate and analyze these.
# Tail Nginx error log sudo tail -f /var/log/nginx/error.log # Tail PHP-FPM logs (path may vary) sudo tail -f /var/log/php/php7.4-fpm.log # Check Redis logs sudo tail -f /var/log/redis/redis-server.log
For Nginx, enabling access_log off; for static assets can reduce log volume. Monitor the X-Cache-Status header added in the Nginx configuration to track cache hit/miss rates.
Application Performance Monitoring (APM)
For deeper insights into WordPress application performance, consider APM tools like New Relic, Datadog, or open-source alternatives like Tideways or Blackfire.io. These tools can pinpoint slow database queries, inefficient PHP functions, and external API calls.