The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB 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 its asynchronous, event-driven architecture, making it exceptionally efficient at handling a large number of concurrent connections, serving static assets, and acting as a reverse proxy. For optimal performance on OVH infrastructure, careful tuning of Nginx is paramount.
Nginx Configuration for Ruby Backends
The core of Nginx’s role is its reverse proxy configuration. This directs incoming HTTP requests to your application server (e.g., Gunicorn for Python, or Puma/Unicorn for Ruby). Here’s a robust Nginx configuration snippet, focusing on key directives for performance and stability.
Core Reverse Proxy Directives
We’ll assume your application server is listening on a local port, say 3000, or a Unix socket. The following configuration prioritizes efficient connection handling, buffering, and upstream health checks.
Example Nginx Configuration Snippet
# Global worker processes and connections
worker_processes auto; # Or a specific number, e.g., 4, based on CPU cores
events {
worker_connections 4096; # Max connections per worker. Adjust based on RAM and expected load.
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn; # Adjust log level as needed
# Performance tuning
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048; # Increase if you have many MIME types
# Gzip compression for static assets and API responses
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
# Buffering settings - crucial for upstream performance
# These control how Nginx buffers responses from the upstream server.
# Adjust based on your application's response times and memory.
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Client request body buffering
client_max_body_size 100M; # Adjust based on expected file uploads
client_body_buffer_size 128k;
# Upstream server definition
upstream ruby_app {
# Option 1: Using a Unix socket (often preferred for local communication)
# server unix:/path/to/your/app.sock fail_timeout=0;
# Option 2: Using a TCP port (e.g., if your app server is on a different host or container)
server 127.0.0.1:3000 fail_timeout=0; # Replace 3000 with your app's port
# Load balancing (if you have multiple app servers)
# least_conn; # Directs requests to the server with the fewest active connections
# ip_hash; # Ensures requests from the same client IP go to the same server
}
# Server block for your application
server {
listen 80;
server_name your_domain.com www.your_domain.com; # Replace with your domain
# Serve static files directly from Nginx for maximum efficiency
location ~ ^/(assets|images|javascripts|stylesheets)/ {
root /path/to/your/rails/public; # Adjust to your Rails public directory
expires 1y;
add_header Cache-Control "public";
}
# Proxy all other requests to the upstream application server
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_read_timeout 300s; # Increase for long-running requests
proxy_connect_timeout 75s;
proxy_send_timeout 300s;
# Point to the upstream defined above
proxy_pass http://ruby_app;
}
# Optional: Handle specific paths differently, e.g., health checks
location /health {
access_log off;
return 200 'OK';
add_header Content-Type text/plain;
}
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /path/to/your/rails/public; # Adjust to your Rails public directory
}
}
# Optional: SSL configuration (highly recommended for production)
# server {
# listen 443 ssl http2;
# server_name your_domain.com www.your_domain.com;
#
# ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_prefer_server_ciphers on;
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
# ssl_session_tickets off;
#
# # ... rest of the server block configuration as above ...
# }
}
Key Directives Explained
worker_processes auto;: Automatically scales Nginx worker processes to the number of CPU cores available. This is generally the best starting point.worker_connections 4096;: Defines the maximum number of simultaneous connections a single worker process can handle. This value, combined withworker_processes, determines the total connection capacity. Ensure this doesn’t exceed available RAM.sendfile on;: Enables efficient transfer of files from disk to network socket without copying data between kernel and user space.tcp_nopush on;: Instructs Nginx to send file contents as few packets as possible, improving efficiency for clients.tcp_nodelay on;: Disables the Nagle algorithm, which can reduce latency by sending small packets immediately.keepalive_timeout 65;: Sets the timeout for persistent connections. A moderate value balances resource usage with client responsiveness.gzip_*directives: Enable and configure Gzip compression for text-based responses, significantly reducing bandwidth and improving load times.proxy_buffer_size,proxy_buffers,proxy_busy_buffers_size: These are critical for managing how Nginx buffers responses from your upstream application. Larger values can improve performance for slow upstream responses but consume more memory. Tuning these requires monitoring your application’s response times and memory usage.proxy_read_timeout,proxy_connect_timeout,proxy_send_timeout: These control how long Nginx will wait for a response from the upstream server. For applications with potentially long-running operations (e.g., report generation, complex calculations), increasing these values is necessary to prevent premature timeouts.upstream ruby_app { ... }: Defines a group of backend servers. Theserverdirective specifies the address and port (or socket) of your application server.fail_timeout=0disables health checks for this specific upstream, which is common when using a single local instance. For multiple instances, you’d configure health checks and load balancing.location / { ... }: This block handles all requests not matched by more specificlocationblocks. It sets essential proxy headers and forwards the request to the defined upstream.location ~ ^/(assets|images|javascripts|stylesheets)/: This directive is crucial for Ruby on Rails applications. It tells Nginx to serve static assets directly from the filesystem (typically thepublic/directory), bypassing the Ruby application entirely. This is a massive performance gain.
Tuning Gunicorn for Ruby Applications (via Rack)
While Gunicorn is primarily known for Python, it can also serve Ruby Rack applications. This is less common than using Puma or Unicorn for Ruby, but if you’re in a mixed-language environment or have specific reasons, here’s how to tune it. The principles are similar to tuning any WSGI/Rack server.
Gunicorn Configuration for Ruby
You’ll typically start Gunicorn using a command-line interface. The key parameters revolve around worker processes, threads, and timeouts.
Example Gunicorn Startup Command
# Assuming your Rack app is in 'config.ru' and you want to listen on a socket
gunicorn --workers 4 \
--threads 2 \
--bind unix:/path/to/your/app.sock \
--timeout 120 \
--keep-alive 5 \
--log-level info \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
config:application
Or, if binding to a TCP port:
gunicorn --workers 4 \
--threads 2 \
--bind 127.0.0.1:3000 \
--timeout 120 \
--keep-alive 5 \
--log-level info \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
config:application
Key Gunicorn Directives Explained
--workers 4: The number of worker processes. A common recommendation is(2 * CPU_cores) + 1. For Ruby, which is often single-threaded per process due to the GIL (Global Interpreter Lock) and MRI’s limitations, more workers might be beneficial than threads.--threads 2: The number of threads per worker. For Ruby, this is less impactful than for Python due to GIL. Setting it to 1 or 2 is typical. If your application performs I/O-bound tasks that can release the GIL (e.g., network requests via `Net::HTTP` or database operations), threads can offer some concurrency within a worker.--bind unix:/path/to/your/app.sockor127.0.0.1:3000: Specifies the network interface and port, or Unix domain socket, Gunicorn will listen on. Unix sockets are generally faster for local communication.--timeout 120: The maximum number of seconds Gunicorn will wait for a worker to process a request before timing out. This should be set higher than your longest expected request time, but not excessively high to avoid hanging workers. It should align with Nginx’sproxy_read_timeout.--keep-alive 5: The number of seconds to keep a worker alive after it has finished processing a request. This can improve performance by reusing workers.config:application: This tells Gunicorn where to find your Rack application. It assumes aconfig.rufile in the current directory, and the application object is namedapplication.
MongoDB Performance Tuning on OVH
MongoDB, a popular NoSQL database, requires careful configuration for optimal performance, especially in a production environment on cloud infrastructure like OVH. Tuning involves both OS-level settings and MongoDB’s configuration file.
OS-Level Tuning for MongoDB
Before touching MongoDB’s configuration, ensure the underlying operating system is optimized. This is crucial for I/O performance and memory management.
1. File Descriptors (ulimit)
MongoDB uses file descriptors for network connections, files, and other OS resources. Insufficient file descriptors can lead to connection errors and performance bottlenecks.
Setting File Descriptors
# Check current limits ulimit -n # Set limits in /etc/security/limits.conf (or a file in /etc/security/limits.d/) # Add these lines for the user running MongoDB (e.g., 'mongodb') # Replace 'mongodb' with the actual user if different. # For production, a value of 64000 or higher is recommended. mongodb soft nofile 64000 mongodb hard nofile 64000 # For systemd services, you might need to configure it in the service unit file # Example for a systemd service file (e.g., /etc/systemd/system/mongodb.service.d/override.conf): # [Service] # LimitNOFILE=64000 # LimitNOFILESoft=64000 # After modifying limits.conf, you may need to log out and log back in, or restart the service. # For systemd, reload daemon and restart MongoDB: # sudo systemctl daemon-reload # sudo systemctl restart mongod
2. Swappiness
Swapping can severely degrade MongoDB performance. It’s generally recommended to disable or minimize swapping.
Disabling Swappiness
# Check current swappiness value cat /proc/sys/vm/swappiness # Set swappiness to 0 (or a very low value like 1) temporarily sudo sysctl vm.swappiness=0 # Make it permanent by editing /etc/sysctl.conf # Add or modify the following line: # vm.swappiness = 0 # Apply the changes sudo sysctl -p
3. Transparent Huge Pages (THP)
THP can cause performance issues with MongoDB due to its memory allocation patterns. It’s recommended to disable it.
Disabling Transparent Huge Pages
# Check THP status cat /sys/kernel/mm/transparent_hugepage/enabled cat /sys/kernel/mm/transparent_hugepage/defrag # Disable THP temporarily echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag # Make it permanent by creating a systemd service or script that runs at boot. # Example for a systemd service file (e.g., /etc/systemd/system/disable-thp.service): # [Unit] # Description=Disable Transparent Huge Pages # Before=local-fs.target # # [Service] # Type=oneshot # ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' # ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag' # # [Install] # WantedBy=multi-user.target # Enable and start the service: # sudo systemctl daemon-reload # sudo systemctl enable disable-thp.service # sudo systemctl start disable-thp.service
MongoDB Configuration File Tuning
The primary configuration file for MongoDB is typically /etc/mongod.conf.
Key Configuration Directives
# /etc/mongod.conf
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true # Essential for durability and performance
# commitInterval: 100 # Default is 100ms. Adjust cautiously.
# engine: wiredTiger # Default and recommended storage engine
# For WiredTiger, consider these:
# wiredTiger:
# collectionConfig:
# cacheSizeGB: 0.75 # Example: 75% of available RAM, minus OS and other processes.
# # Start conservatively and monitor.
# engineConfig:
# cacheSizeGB: 0.75 # Same as above, for the overall WiredTiger cache.
# # concurrentReadTransactions: 16 # Default is 16. Adjust based on read load.
# # concurrentWriteTransactions: 16 # Default is 16. Adjust based on write load.
# Network settings
net:
port: 27017
bindIp: 127.0.0.1,192.168.x.x # Bind to localhost and specific internal IPs for security.
# Avoid binding to 0.0.0.0 unless absolutely necessary and secured.
# Logging
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
verbosity: 0 # 0 is default, higher values increase log detail (and I/O)
quiet: false
# Security (essential for production)
# security:
# authorization: enabled # Enable authentication
# Sharding (if applicable)
# sharding:
# clusterRole: configsvr # or shardsvr
# Replication (if applicable)
# replication:
# replSetName: myReplicaSetName
Key MongoDB Directives Explained
storage.journal.enabled: true: Enables the journal, which is critical for data durability and performance. It allows MongoDB to recover from crashes quickly.wiredTiger.collectionConfig.cacheSizeGBandwiredTiger.engineConfig.cacheSizeGB: These directives control the size of the WiredTiger cache. The WiredTiger cache stores data and index blocks in RAM for faster access. A common starting point is to allocate 50-75% of the server’s RAM to this cache, ensuring enough memory is left for the OS and other processes. Monitor memory usage closely.net.bindIp: RestrictingbindIpto specific internal IP addresses (e.g.,127.0.0.1for local access and internal network IPs) significantly enhances security. Never bind to0.0.0.0without robust firewall rules and authentication.systemLog.verbosity: Higher verbosity levels provide more detailed logs, which can be useful for debugging but increase disk I/O. Keep it at 0 for production unless troubleshooting.security.authorization: enabled: Always enable authentication in production environments to protect your data.
Monitoring and Iterative Tuning
Tuning is not a one-time event. Continuous monitoring is essential to identify bottlenecks and validate the effectiveness of your changes. Key metrics to watch include:
Key Monitoring Metrics
- Nginx: Request rates, error rates (4xx, 5xx), connection counts, upstream response times, worker connections, cache hit rates (if applicable). Tools like
nginx-module-vtsor Prometheus exporters are invaluable. - Application Server (Gunicorn/Puma/Unicorn): Request latency, worker utilization, memory usage, garbage collection activity (for Ruby), number of active requests.
- MongoDB: Query performance (slow queries), read/write operations per second, cache hit ratio, disk I/O, network traffic, connection counts, oplog size (for replication). MongoDB’s built-in `mongostat` and `mongotop` commands, along with tools like Percona Monitoring and Management (PMM) or Datadog, are crucial.
- System Resources: CPU utilization, memory usage, disk I/O wait times, network throughput. Tools like
htop,vmstat,iostat, and Prometheus Node Exporter are standard.
When making changes, adjust one parameter at a time and observe the impact. Use load testing tools (e.g., ApacheBench `ab`, `wrk`, k6) to simulate production traffic and measure performance under stress. This iterative approach ensures you achieve optimal performance tailored to your specific application workload and OVH infrastructure.