• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB on OVH for Ruby

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 with worker_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. The server directive specifies the address and port (or socket) of your application server. fail_timeout=0 disables 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 specific location blocks. 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 the public/ 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.sock or 127.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’s proxy_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 a config.ru file in the current directory, and the application object is named application.

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.cacheSizeGB and wiredTiger.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: Restricting bindIp to specific internal IP addresses (e.g., 127.0.0.1 for local access and internal network IPs) significantly enhances security. Never bind to 0.0.0.0 without 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-vts or 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.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala