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

Vengala Vinay

Having 9+ Years of Experience in Software Development

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

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on OVH for Ruby

Nginx Configuration for Ruby Applications

Optimizing Nginx as a reverse proxy for Ruby applications, particularly those served by Gunicorn or Puma, is crucial for handling concurrent requests efficiently. The primary goals are to minimize latency, maximize throughput, and ensure graceful handling of upstream server failures. We’ll focus on key directives that impact performance and stability.

Worker Processes and Connections

The number of worker processes and the maximum number of connections per worker are fundamental tuning parameters. A common starting point is to set worker_processes to the number of CPU cores available on the server. For I/O-bound applications, increasing this slightly might be beneficial, but excessive values can lead to context-switching overhead.

The worker_connections directive defines the maximum number of simultaneous connections that each worker process can open. This value, multiplied by worker_processes, gives the total maximum connections Nginx can handle. A typical value is 1024, but this can be increased significantly if your server has ample RAM and your application’s upstream servers can handle the load. Ensure that the operating system’s file descriptor limit (ulimit -n) is set high enough to accommodate this.

Example Nginx Configuration Snippet

Add these directives to your nginx.conf file, typically within the events block:

events {
    worker_connections 4096; # Adjust based on server resources and expected load
    multi_accept on;        # Allows workers to accept multiple connections at once
}

Keepalive Connections

Enabling HTTP keep-alive connections reduces the overhead of establishing new TCP connections for each request. This is particularly beneficial for Ruby applications where multiple requests might be made in quick succession.

Example Nginx Keepalive Configuration

Within the http block:

http {
    # ... other http configurations ...

    keepalive_timeout 65; # Time to keep a keepalive connection open
    keepalive_requests 100; # Max requests per keepalive connection

    # ... rest of http configuration ...
}

Buffering and Timeouts

Buffering directives control how Nginx handles request and response bodies. While buffering can improve performance by allowing Nginx to send data to the client more efficiently, it can also increase memory usage. For large file uploads or streaming, you might need to adjust these.

Timeouts are critical for preventing Nginx from holding onto connections indefinitely, especially if an upstream server is slow or unresponsive. Setting appropriate timeouts ensures that Nginx can free up resources and handle other requests.

Key Buffering and Timeout Directives

  • client_body_buffer_size: Sets the buffer size for client request bodies.
  • client_max_body_size: Maximum allowed size of a client request body.
  • proxy_connect_timeout: Timeout for establishing a connection with the upstream server.
  • proxy_send_timeout: Timeout for transmitting a request to the upstream server.
  • proxy_read_timeout: Timeout for reading a response from the upstream server.
  • send_timeout: Timeout for transmitting a response to the client.

Example Nginx Proxy Configuration

These are typically placed within your server or location block that proxies to your Ruby application:

location / {
    proxy_pass http://your_ruby_app_upstream;
    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;

    client_body_buffer_size 10K;
    client_max_body_size 100M; # Adjust as needed for file uploads

    proxy_connect_timeout 5s;
    proxy_send_timeout 10s;
    proxy_read_timeout 10s;
    send_timeout 10s;
}

Gunicorn/Puma Configuration for Ruby

The application server (Gunicorn for Python, but often used metaphorically for Ruby WSGI/Rack servers like Puma or Unicorn) plays a vital role in how your Ruby application handles requests. Tuning its worker count, threads, and timeouts is essential.

Worker and Thread Management

For Puma, the most common Ruby web server, you’ll configure workers and threads. A common strategy is to have a number of worker processes equal to the number of CPU cores, and then use threads within each worker to handle concurrent requests. The optimal ratio depends heavily on your application’s I/O patterns and CPU-bound tasks.

Puma Configuration Example

This can be set via command-line arguments or a config/puma.rb file.

# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }.to_i
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count

preload_app!
# ... other puma configurations ...

And started with:

WEB_CONCURRENCY=4 RAILS_MAX_THREADS=8 bundle exec puma -C config/puma.rb

For Unicorn, a process-based server, you’d typically set the number of workers to match CPU cores and rely on the OS for concurrency. Threads are not directly managed by Unicorn itself.

Timeouts and Worker Behavior

Application servers also have their own timeout mechanisms. These prevent a single slow request from blocking a worker indefinitely. It’s important to align these with Nginx’s proxy timeouts.

Puma Timeout Example

Puma’s worker_timeout directive (in seconds) specifies how long a worker can be idle before being restarted. This is different from request timeouts.

# config/puma.rb
worker_timeout 60 # Restart worker after 60 seconds of inactivity

PostgreSQL Tuning for Ruby Applications

Database performance is often the bottleneck for web applications. Tuning PostgreSQL involves adjusting shared memory, connection pooling, and query optimization. On OVH, you’ll have access to dedicated instances or managed PostgreSQL services, allowing for direct configuration adjustments.

Shared Memory and Buffers

shared_buffers is arguably the most critical parameter. It dictates how much memory PostgreSQL can use for caching data pages. A common recommendation is 25% of system RAM, but this can be increased up to 40% on dedicated database servers with sufficient RAM, provided the OS has enough memory for its own caching and other processes.

work_mem controls the amount of memory used for internal sort operations and hash tables before writing to temporary disk files. Setting this too high can lead to excessive memory consumption if many complex queries run concurrently. Setting it too low can cause performance degradation due to disk spills.

Example PostgreSQL Configuration (postgresql.conf)

Edit your postgresql.conf file. The exact location varies by OS and PostgreSQL version (e.g., /etc/postgresql/14/main/postgresql.conf on Debian/Ubuntu).

# Example for a server with 32GB RAM
shared_buffers = 8GB       # 25% of 32GB
work_mem = 64MB            # Adjust based on query complexity and concurrency
maintenance_work_mem = 256MB # For VACUUM, CREATE INDEX, etc.
effective_cache_size = 24GB # ~75% of total RAM, hints to the planner about OS cache

Connection Pooling

Establishing a new database connection is an expensive operation. Connection pooling significantly improves performance by reusing existing connections. While your Ruby application framework (e.g., Rails) has built-in connection pooling, using a dedicated external pooler like PgBouncer can offer more advanced control and efficiency, especially under high load.

PgBouncer Configuration Example

Install PgBouncer and configure its pgbouncer.ini file.

[databases]
mydb = host=your_db_host port=5432 dbname=your_db_name user=your_db_user password=your_db_password

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = session # or transaction, depending on application needs
max_client_conn = 2000 # Should be higher than your application's max connections
default_pool_size = 20 # Pool size per database
min_pool_size = 5
pool_timeout = 300

# Example userlist.txt
"your_db_user" "md5" "hashed_password_for_pgbouncer_user"

Your Ruby application’s database configuration would then point to the PgBouncer host and port (e.g., localhost:6432).

Query Optimization and Monitoring

Even with optimal server configurations, inefficient queries can cripple performance. Regularly analyze slow queries using PostgreSQL’s logging features and tools like pg_stat_statements. Ensure your application uses efficient ORM patterns and avoids N+1 query problems.

Enabling pg_stat_statements

Add to postgresql.conf:

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
pg_stat_statements.max = 10000

Then, in psql:

CREATE EXTENSION pg_stat_statements;

You can then query pg_stat_statements to identify problematic queries.

OVH Specific Considerations

When deploying on OVH, pay attention to network latency between your application servers and database servers, especially if they are in different availability zones or regions. Utilize OVH’s monitoring tools to track CPU, memory, disk I/O, and network traffic for all components. For managed PostgreSQL services, OVH often provides pre-tuned configurations, but understanding these underlying parameters is still essential for effective troubleshooting.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala