The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on OVH for PHP
Nginx as a High-Performance Frontend for PHP Applications
When deploying PHP applications, especially those leveraging modern frameworks and APIs, Nginx serves as an exceptionally efficient frontend. Its asynchronous, event-driven architecture excels at handling a high volume of concurrent connections, offloading the heavy lifting from your application servers. This section details crucial Nginx tuning parameters for optimal PHP performance on OVH infrastructure.
Core Nginx Configuration Tuning
The primary configuration file, typically located at /etc/nginx/nginx.conf, contains global directives that influence worker processes and connection handling. For production environments, especially on OVH’s robust network, we can push these limits further.
Worker Processes and Connections
The worker_processes directive should ideally be set to the number of CPU cores available on your server. This allows Nginx to utilize all available processing power. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can handle. The total maximum connections will be worker_processes * worker_connections.
Consider the following snippet for /etc/nginx/nginx.conf:
user www-data;
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; # Adjust based on expected load and server RAM
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m; # Adjust size based on traffic
ssl_session_timeout 10m;
# 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;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Note: On OVH, especially with dedicated servers, you have direct access to hardware. Use lscpu to determine the number of CPU cores and adjust worker_processes accordingly. For worker_connections, monitor your server’s memory usage. A value of 4096 is a good starting point, but can be increased if RAM permits and load demands it.
Optimizing PHP-FPM/Gunicorn Configuration
Whether you’re using PHP-FPM for traditional PHP or Gunicorn for Python/WSGI applications, the principles of process management and resource allocation are similar. These application servers are the workhorses that execute your PHP/Python code.
PHP-FPM Tuning
The PHP-FPM configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf) is critical. The pm (process manager) setting dictates how FPM manages child processes. For production, ondemand or dynamic are generally preferred over static, as they scale based on demand, conserving resources during low traffic periods.
Process Manager Settings
Here’s a sample configuration for www.conf using the dynamic process manager:
; Start a new child until the pm.max_children limit is reached. ; pm = dynamic ; The number of child processes that will be used when pm = dynamic. ; pm.max_children = 50 ; Adjust based on available RAM and CPU cores. A good starting point is (total_ram_mb / avg_process_size_mb) / num_cores. ; The number of *additional* child processes which will be spawned when the following requests are executed. ; pm.start_servers = 2 ; The minimum number of children that should always be running. ; pm.min_spare_servers = 1 ; The maximum number of children that should always be running. ; pm.max_spare_servers = 5 ; The number of requests each child process should execute before respawning. ; This helps to prevent memory leaks. pm.max_requests = 500 ; The timeout for serving a request. request_terminate_timeout = 60 ; seconds ; The timeout for acquiring a child from the pool. pm.process_idle_timeout = 10s ; seconds
Tuning Strategy:
pm.max_children: This is the most critical parameter. Calculate it by estimating the average memory footprint of a PHP-FPM worker process (e.g., usingps aux --sort=-%mem | grep php-fpmduring peak load) and dividing your server’s available RAM by this figure. Then, consider your CPU cores. A common heuristic is to aim for a total number of children that your CPUs can reasonably handle concurrently. Start conservatively and monitor.pm.start_servers,pm.min_spare_servers,pm.max_spare_servers: These control the dynamic scaling. Adjust them to ensure a quick response to traffic spikes without over-provisioning.pm.max_requests: Essential for long-running applications to mitigate memory leaks. 500 is a common value; adjust based on application stability.request_terminate_timeout: Set this to a reasonable value (e.g., 60 seconds) to prevent hung requests from holding up worker processes indefinitely.
Gunicorn Tuning (for Python/WSGI)
Gunicorn’s worker class and number of workers are key. The sync worker class is simple but blocking. For I/O-bound applications, gevent or eventlet (asynchronous workers) are highly recommended. The number of workers is typically set to (2 * number_of_cores) + 1 as a starting point.
Example Gunicorn command line:
gunicorn --workers 3 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application
Tuning Strategy:
--workers: Start with(2 * CPU cores) + 1. Monitor CPU and memory usage.--worker-class: For I/O-bound tasks (API calls, database queries),geventoreventletare superior tosync. Ensure you have the necessary libraries installed (e.g.,pip install gevent).--bind: This should typically bind to a local interface (e.g.,127.0.0.1:8000) and be proxied by Nginx.--threads: If using a synchronous worker class (likesync), you can use threads to handle concurrency within a worker. However, for I/O-bound tasks, asynchronous workers are generally more efficient.
Redis as a High-Performance Cache and Session Store
Redis is an indispensable tool for reducing database load and speeding up application response times. Proper configuration on OVH servers ensures it’s a reliable and fast component of your stack.
Redis Configuration Tuning (redis.conf)
The main configuration file is typically /etc/redis/redis.conf. Key parameters to focus on include memory management, persistence, and network settings.
Memory Management
maxmemory is crucial to prevent Redis from consuming all available RAM. Set this to a value that leaves ample memory for the OS and other services. maxmemory-policy determines how Redis evicts keys when maxmemory is reached.
# Set a limit on memory usage maxmemory 2gb ; Adjust based on server RAM and other services. Leave at least 20-30% for OS. # When maxmemory limit is reached, Redis will try to evict a key based on the policy. # allkeys-lru: Evict using an approximation of Least Recently Used (LRU) algorithm. Good for general caching. # volatile-lru: Evict only keys with an expire set, using LRU. # allkeys-random: Evict a random key. # volatile-random: Evict a random key with an expire set. # volatile-ttl: Evict keys with an expire set, prioritizing those with shortest TTL. maxmemory-policy allkeys-lru
Persistence
For caching scenarios where data loss is acceptable, disabling or minimizing persistence can improve performance. If Redis is used for critical data or session storage, configure RDB snapshots and/or AOF logging appropriately.
# Disable RDB snapshots if Redis is purely for caching and data loss is acceptable. # save "" # If using RDB, adjust the save points to balance performance and data safety. # save 900 1 # save 300 10 # save 60 10000 # Append Only File (AOF) logging. More durable but can impact write performance. # appendonly no ; Set to 'yes' for higher durability. # AOF fsync policy: # everysec: fsync every second (default). Good balance. # always: fsync after every write command. Slowest but safest. # no: Never fsync. Relies on OS. Fastest but least safe. # appendfsync everysec
Recommendation for Caching: If Redis is solely for caching, consider disabling RDB snapshots (save "") and setting appendonly no. This significantly reduces disk I/O and improves performance. If it’s for sessions or critical data, use appendonly yes with appendfsync everysec and appropriate RDB save points.
Network and Performance
Tuning tcp-backlog can help handle bursts of new connections. Ensure Redis is bound to a specific IP address for security, especially if it’s not directly exposed to the internet.
# Set the TCP backlog setting. # tcp-backlog 511 ; Default is 511. Can be increased on busy servers. # Bind to a specific IP address for security. # bind 127.0.0.1 ; Or the private IP of your OVH server if accessed from other internal machines. # bind 192.168.1.100 127.0.0.1
Putting It All Together: Nginx, PHP-FPM/Gunicorn, and Redis Integration
The synergy between these components is where true performance gains are realized. Nginx acts as the gatekeeper, efficiently passing requests to PHP-FPM/Gunicorn, which then leverages Redis for fast data retrieval and storage.
Nginx to PHP-FPM/Gunicorn Proxy Configuration
Your Nginx site configuration (e.g., /etc/nginx/sites-available/your-app) needs to be set up to proxy requests correctly. For PHP-FPM, this involves FastCGI. For Gunicorn, it’s HTTP proxying.
Nginx with PHP-FPM Example
server {
listen 80;
server_name your-domain.com www.your-domain.com;
root /var/www/your-app/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# Make sure this matches your PHP-FPM pool socket or address:port
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
location ~ /\.ht {
deny all;
}
# Caching static assets with Nginx
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
}
Nginx with Gunicorn Example
server {
listen 80;
server_name your-domain.com www.your-domain.com;
location / {
proxy_pass http://127.0.0.1:8000; # Assuming Gunicorn is bound to 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;
}
# Caching static assets with Nginx
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
}
Integrating Redis for Caching and Sessions
In your PHP application (e.g., using Symfony, Laravel, or custom code), configure your cache and session handlers to use Redis. This typically involves installing a Redis client library (like phpredis or Predis) and updating your application’s configuration.
Example (Conceptual PHP):
<?php
// Assuming you have a Redis client library installed and configured
// For caching
$redis = new Redis();
$redis->connect('127.0.0.1', 6379); // Connect to your Redis instance
// Example: Storing and retrieving a cached item
$cacheKey = 'user_data_' . $userId;
$cachedData = $redis->get($cacheKey);
if ($cachedData === false) {
// Data not in cache, fetch from DB
$data = fetchUserDataFromDatabase($userId);
// Store in Redis with an expiration time (e.g., 1 hour)
$redis->setex($cacheKey, 3600, json_encode($data));
} else {
$data = json_decode($cachedData, true);
}
// For sessions (e.g., in Symfony's config/packages/session.yaml)
// framework:
// session:
// handler_id: session.storage.redis
// save_path: 'tcp://127.0.0.1:6379?timeout=2.5&read_timeout=10'
// Or using Predis
// use Predis\Client;
// $redis = new Client([
// 'scheme' => 'tcp',
// 'host' => '127.0.0.1',
// 'port' => 6379,
// ]);
// $redis->set('my_key', 'my_value', 'ex', 3600); // Set with expiration
?>
Monitoring and Diagnostics
Continuous monitoring is key to identifying bottlenecks and ensuring your tuning efforts are effective. Utilize tools like:
- Nginx:
nginx -s reloadfor configuration changes,/var/log/nginx/access.loganderror.log, and Nginx status module. - PHP-FPM:
systemctl status php8.1-fpm, PHP-FPM logs (often in/var/log/php8.1-fpm.log), and the FPM status page. - Gunicorn: Application logs, and process monitoring tools like
htoporsupervisorctl status. - Redis:
redis-cli INFO memory,redis-cli INFO stats,redis-cli slowlog get 10, andredis-cli MONITOR(use with caution in production). - System:
top,htop,vmstat,iostat, andnetstatare invaluable for understanding overall system resource utilization.
On OVH, leverage their monitoring tools as well, particularly for network traffic and hardware health. Regularly review these metrics to fine-tune your configurations further.