The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on OVH for C
Nginx as a High-Performance Reverse Proxy and Load Balancer
When deploying applications on OVH, particularly those leveraging Python (with Gunicorn) or PHP (with FPM), Nginx serves as the indispensable front-end. Its efficiency in handling static assets, SSL termination, and request routing is paramount. We’ll focus on tuning Nginx for optimal performance in this specific environment.
Nginx Configuration Tuning
The core of Nginx performance tuning lies within its nginx.conf file, typically located at /etc/nginx/nginx.conf or within /etc/nginx/conf.d/. We’ll adjust key worker processes and connection parameters.
Worker Processes and Connections
The worker_processes directive should ideally be set to the number of CPU cores available on your OVH instance. This allows Nginx to utilize all available processing power for handling requests concurrently. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can open. A common starting point is 1024, but this can be increased based on expected traffic load and system memory.
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; # Increased from default 1024
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 as needed
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/*;
}
Explanation:
worker_processes auto;: Automatically sets the number of worker processes to the number of available CPU cores.worker_connections 4096;: Significantly increases the connection limit per worker, allowing for more concurrent requests.sendfile on;: Enables efficient transfer of files from disk to network socket without user-space buffering.tcp_nopush on;: Instructs Nginx to send header and file in one packet if possible.tcp_nodelay on;: Disables the Nagle algorithm, reducing latency for small packets.keepalive_timeout 65;: Sets the keep-alive timeout for client connections.gzip on;: Enables Gzip compression for responses.gzip_types ...;: Specifies the MIME types to be compressed.ssl_session_cache shared:SSL:10m;: Configures a shared SSL session cache for improved handshake performance.
Caching and Static File Serving
Nginx excels at serving static files. Configuring appropriate caching headers and directives can offload significant load from your application servers.
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
log_not_found off;
}
Explanation:
expires 30d;: Sets theExpiresheader to 30 days in the future, instructing browsers to cache these assets.add_header Cache-Control "public, immutable";: Provides more granular control over caching, indicating that the resource is publicly cacheable and its content will not change.access_log off;andlog_not_found off;: Disables logging for static assets to reduce disk I/O and improve performance.
Gunicorn Tuning for Python Applications
Gunicorn (Green Unicorn) is a popular WSGI HTTP Server for Python. Its performance is heavily influenced by the number of worker processes and the type of worker class used.
Worker Processes and Threads
The --workers flag determines the number of worker processes Gunicorn will spawn. A common recommendation is (2 * CPU_CORES) + 1. For I/O-bound applications, using --threads with the gthread worker class can improve concurrency without the overhead of multiple processes.
# Example command to start Gunicorn
gunicorn --workers 5 \
--bind 0.0.0.0:8000 \
--worker-class sync \
--timeout 120 \
your_project.wsgi:application
# For threaded workers (if your application is I/O bound)
gunicorn --workers 2 \
--threads 4 \
--bind 0.0.0.0:8000 \
--worker-class gthread \
--timeout 120 \
your_project.wsgi:application
Explanation:
--workers 5: Spawns 5 worker processes. Adjust based on your OVH instance’s CPU cores.--bind 0.0.0.0:8000: Listens on all network interfaces on port 8000. Nginx will proxy to this.--worker-class sync: The default and most common worker class, suitable for CPU-bound tasks.--worker-class gthread: Uses threads for concurrency, beneficial for I/O-bound applications. Requires careful consideration of GIL limitations.--threads 4: Specifies the number of threads per worker when usinggthread.--timeout 120: Sets the worker timeout to 120 seconds. Adjust based on your application’s longest-running requests.
Gunicorn Configuration File
For more complex configurations, using a Python configuration file is recommended. This file can be loaded using the -c flag.
# gunicorn_config.py import multiprocessing bind = "0.0.0.0:8000" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "sync" # or "gthread" threads = 4 # if using gthread timeout = 120 loglevel = "info" accesslog = "-" # Log to stdout errorlog = "-" # Log to stderr # Example for gthread # workers = 2 # threads = 4 # worker_class = "gthread"
# Example command using config file gunicorn -c gunicorn_config.py your_project.wsgi:application
PHP-FPM Tuning
For PHP applications, PHP-FPM (FastCGI Process Manager) is the standard. Tuning its process management and performance settings is crucial.
PHP-FPM Pool Configuration
The primary configuration file for PHP-FPM pools is typically found in /etc/php/[version]/fpm/pool.d/www.conf. Key directives to adjust include the process manager settings.
; /etc/php/8.1/fpm/pool.d/www.conf [www] user = www-data group = www-data listen = /run/php/php8.1-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 2 pm.max_spare_servers = 10 pm.process_idle_timeout = 10s pm.max_requests = 500 ; For static process management (use with caution, can be less flexible) ; pm = static ; pm.max_children = 20 ; Other performance-related settings request_terminate_timeout = 120 ; rlimit_files = 1024 ; rlimit_nofile = 65536
Explanation:
pm = dynamic: PHP-FPM will dynamically manage the number of child processes based on load.pm.max_children: The maximum number of child processes that will be created. This is a critical setting and should be tuned based on server memory and CPU. Too high can lead to OOM errors.pm.start_servers: The number of child processes to start when PHP-FPM starts.pm.min_spare_servers: The minimum number of idle supervisor processes.pm.max_spare_servers: The maximum number of idle supervisor processes.pm.process_idle_timeout: The number of seconds after which an idle process will be killed.pm.max_requests: The number of requests each child process should execute before respawning. This helps prevent memory leaks.request_terminate_timeout: The number of seconds a script is allowed to run before it is terminated.
Choosing the Right Process Manager
Dynamic PM: Recommended for most use cases. It balances resource usage by scaling processes up and down. Tune max_children carefully. A common starting point is (total_memory_in_MB / average_process_memory_in_MB) / 2, but empirical testing is key.
Static PM: Useful for predictable, high-traffic environments where you want to ensure a fixed number of processes are always available. This can reduce latency by eliminating process startup time but may waste resources if traffic is low.
On-Demand PM: (Less common for high-performance scenarios) Processes are spawned only when a request is received and killed after a period of inactivity. Good for saving memory but can introduce latency.
Redis Performance Tuning
Redis is an in-memory data structure store, often used as a cache, message broker, and database. Optimizing its configuration on OVH is vital for fast data retrieval.
Redis Configuration File
The primary configuration file is redis.conf, typically located at /etc/redis/redis.conf.
# /etc/redis/redis.conf # General daemonize yes pidfile /var/run/redis/redis-server.pid logfile /var/log/redis/redis-server.log loglevel notice # Network bind 127.0.0.1 ::1 # Bind to localhost for security if Nginx/App are on the same server # bind 0.0.0.0 # If Redis is on a separate server or needs external access (use with firewall) port 6379 tcp-backlog 511 # Default is 511, can be increased if OS allows # Memory Management maxmemory 256mb # Set a limit to prevent Redis from consuming all system RAM maxmemory-policy allkeys-lru # Eviction policy: LRU (Least Recently Used) is common # Persistence (adjust based on needs) save "" # Disable RDB persistence if only using as a cache appendonly no # Disable AOF persistence if only using as a cache # Performance tcp-keepalive 300 # Keep TCP connections alive for 5 minutes databases 16Explanation:
bind 127.0.0.1 ::1: Restricts Redis to listen only on the local loopback interface. Crucial for security if your application and Redis are on the same OVH instance. If on separate instances, use a private IP and secure with a firewall.maxmemory 256mb: Sets a hard limit on Redis memory usage. Essential to prevent it from consuming all available RAM and causing system instability. Adjust this value based on your OVH instance's RAM and your application's caching needs.maxmemory-policy allkeys-lru: Whenmaxmemoryis reached, Redis will evict the least recently used keys. Other policies includevolatile-lru,allkeys-random, etc.save ""andappendonly no: If Redis is purely used as a cache and data loss on restart is acceptable, disabling persistence significantly reduces disk I/O and can improve performance.tcp-backlog 511: The maximum number of pending connections. Increasing this can help under heavy load, but requires OS-level tuning (e.g.,net.core.somaxconn).tcp-keepalive 300: Keeps idle TCP connections open for a longer period, reducing the overhead of establishing new connections for frequent Redis operations.
Monitoring Redis
Regularly monitor Redis performance using redis-cli INFO and tools like RedisInsight or Prometheus with the Redis exporter. Key metrics include:
used_memory: Current memory usage.mem_fragmentation_ratio: Indicates memory fragmentation. A ratio > 1.5 might suggest memory issues.instantaneous_ops_per_sec: Current operations per second.keyspace_hitsandkeyspace_misses: Crucial for cache hit ratio. Aim for a high hit ratio.
redis-cli 127.0.0.1:6379> INFO memory 127.0.0.1:6379> INFO stats 127.0.0.1:6379> INFO persistence 127.0.0.1:6379> INFO commandstats # To see performance of individual commands
Putting It All Together: OVH Deployment Strategy
On an OVH instance, a typical setup would involve:
- Nginx: Configured as a reverse proxy, handling SSL termination, static file serving, and forwarding dynamic requests to Gunicorn/PHP-FPM. It should listen on port 80/443.
- Gunicorn/PHP-FPM: Running on a non-privileged port (e.g., 8000 for Gunicorn, 9000 for PHP-FPM) or via a Unix socket. Nginx will proxy requests to these. Ensure they are configured to run as a non-root user (e.g.,
www-data). - Redis: Running on
127.0.0.1(if on the same server) with appropriatemaxmemoryand eviction policies.
Example Nginx Virtual Host Configuration:
# /etc/nginx/sites-available/your_app
server {
listen 80;
server_name your_domain.com www.your_domain.com;
# Redirect HTTP to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name your_domain.com www.your_domain.com;
# SSL Configuration (ensure you have certs)
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Static files
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
root /var/www/your_app/public; # Adjust path to your static assets
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
log_not_found off;
}
# Dynamic requests to Gunicorn
location / {
proxy_pass http://127.0.0.1:8000; # For Gunicorn
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;
proxy_read_timeout 300s; # Increase timeout if needed
proxy_connect_timeout 75s;
}
# Dynamic requests to PHP-FPM
# location ~ \.php$ {
# include snippets/fastcgi-php.conf;
# fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Adjust PHP version
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# include fastcgi_params;
# fastcgi_read_timeout 300s;
# }
root /var/www/your_app/public; # Default root for PHP-FPM
index index.php index.html index.htm;
}
By meticulously tuning these components—Nginx for efficient request handling, Gunicorn/PHP-FPM for application execution, and Redis for fast caching—you can achieve a highly performant and scalable infrastructure on OVH.