The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on OVH for Ruby
Nginx as a High-Performance Frontend for Ruby Applications
When deploying Ruby applications, particularly those built with frameworks like Ruby on Rails or Sinatra, Nginx serves as an indispensable frontend. Its strengths lie in efficient static file serving, SSL termination, request buffering, and load balancing. For optimal performance on OVH infrastructure, we’ll focus on tuning Nginx for maximum throughput and minimal latency.
Nginx Configuration Tuning
The core of Nginx performance tuning lies within its configuration files, primarily nginx.conf and site-specific configurations in sites-available/sites-enabled. We’ll focus on key directives that impact connection handling, buffering, and worker processes.
Worker Processes and Connections
The worker_processes directive dictates how many worker processes Nginx will spawn. Setting this to auto is generally recommended, allowing Nginx to detect the number of CPU cores and utilize them efficiently. The worker_connections directive limits the number of simultaneous connections a single worker process can handle. This value should be set high enough to accommodate peak traffic but not so high as to exhaust system resources.
Buffering Directives
Buffering is crucial for handling slow clients and managing upstream communication. Directives like client_body_buffer_size, client_header_buffer_size, large_client_header_buffers, and proxy_buffers control the size and number of buffers used for client requests and proxy responses. Tuning these can prevent request timeouts and improve data transfer efficiency.
Keepalive Connections
Enabling HTTP keep-alive connections (keepalive_timeout and keepalive_requests) allows Nginx to reuse existing TCP connections for multiple HTTP requests, significantly reducing the overhead of establishing new connections. This is particularly beneficial for clients making numerous small requests.
Example Nginx Configuration Snippet
Here’s a sample snippet demonstrating these tuning parameters within an Nginx server block proxying to a Gunicorn/FPM backend. Adjust values based on your OVH instance’s CPU, RAM, and expected traffic load.
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
server_tokens off;
client_body_buffer_size 10m;
client_header_buffer_size 1m;
large_client_header_buffers 4 1m;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# ... other http directives ...
server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://unix:/path/to/your/app.sock; # Or http://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;
proxy_redirect off;
}
# Serve static assets directly
location ~ ^/(assets|images|javascripts|stylesheets|system)/ {
root /path/to/your/public;
expires 1y;
add_header Cache-Control "public";
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
}
Gunicorn/FPM Tuning for Ruby Backends
For Ruby applications, Gunicorn is a popular WSGI HTTP Server. If your application is PHP-based, you’ll be using PHP-FPM. The tuning strategies for these differ but share the common goal of efficiently managing worker processes to handle incoming requests.
Gunicorn Tuning
Gunicorn’s performance is heavily influenced by its worker class and the number of worker processes. For CPU-bound applications, the sync worker class is common, but for I/O-bound tasks, gevent or eventlet can offer better concurrency. The --workers flag determines the number of worker processes. A common starting point is (2 * CPU_CORES) + 1, but this should be adjusted based on application behavior and available memory.
PHP-FPM Tuning
PHP-FPM uses pools of worker processes to handle requests. The pm (process manager) setting is critical. Options include static, dynamic, and ondemand. For predictable high-traffic sites, static can offer the lowest latency. For variable loads, dynamic with carefully tuned pm.max_children, pm.start_servers, pm.min_spare_servers, and pm.max_spare_servers is often preferred. pm.max_requests limits the number of requests a child process will handle before respawning, helping to prevent memory leaks.
Example Gunicorn Configuration (Command Line)
gunicorn --workers 4 \
--worker-class gevent \
--bind unix:/path/to/your/app.sock \
--timeout 120 \
your_app.wsgi:application
Example PHP-FPM Configuration (www.conf)
; Example for /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.max_requests = 500 ; Other directives like request_terminate_timeout, etc. ; request_terminate_timeout = 120s
Redis Performance Optimization
Redis is a powerful in-memory data structure store often used for caching, session management, and message brokering. Optimizing Redis on OVH involves careful configuration of its memory usage, persistence, and network settings.
Memory Management
The maxmemory directive is crucial for preventing Redis from consuming all available RAM. Setting this to a reasonable percentage of your instance’s total RAM (e.g., 70-80%) is a good practice. The maxmemory-policy determines how Redis evicts keys when maxmemory is reached. allkeys-lru (Least Recently Used) is a common and effective choice for caching scenarios.
Persistence
Redis offers two persistence mechanisms: RDB (snapshotting) and AOF (Append Only File). For high-performance, low-latency applications, disabling or minimizing RDB snapshots (e.g., setting save "") and using AOF with appendfsync everysec can provide a good balance between durability and performance. If durability is paramount, appendfsync always offers the strongest guarantees but at a performance cost.
Network and Client Settings
The tcp-backlog directive can be increased to handle a larger queue of incoming TCP connections, especially under heavy load. For Ruby applications, ensuring your Redis client library is configured efficiently (e.g., using connection pooling) is also vital.
Example Redis Configuration Snippet (redis.conf)
# Example for /etc/redis/redis.conf daemonize yes pidfile /var/run/redis/redis-server.pid port 6379 tcp-backlog 511 # Memory Management maxmemory 4gb # Adjust based on your OVH instance RAM maxmemory-policy allkeys-lru # Persistence save "" # Disable RDB snapshots for maximum performance if AOF is used appendonly yes appendfilename "appendonly.aof" appendfsync everysec # Good balance between durability and performance # Logging loglevel notice logfile /var/log/redis/redis-server.log # Security (basic example, consider more robust measures) # requirepass your_strong_password bind 127.0.0.1 ::1 # Bind to localhost if only local access is needed
Putting It All Together: A Holistic Approach
Achieving peak performance on OVH for your Ruby stack requires a coordinated effort across Nginx, your application server (Gunicorn/FPM), and Redis. Regularly monitor key metrics such as CPU utilization, memory usage, network I/O, request latency, and error rates. Use tools like htop, vmstat, Nginx’s stub_status module, Gunicorn’s stats, and Redis’s INFO command to identify bottlenecks. Iteratively tune these components, testing the impact of each change in a staging environment before deploying to production. This playbook provides a solid foundation for building a robust and high-performing Ruby application infrastructure on OVH.