• 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 Redis on Linode for Ruby

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on Linode for Ruby

Optimizing Ruby Web Applications on Linode: A Deep Dive into Nginx, Gunicorn/FPM, and Redis Tuning

This playbook focuses on achieving peak performance for Ruby web applications deployed on Linode, specifically targeting the critical components: Nginx as the reverse proxy, Gunicorn (for Rails/Sinatra with WSGI) or PHP-FPM (for Ruby-based PHP applications, less common but possible) as the application server, and Redis for caching and session management. We’ll move beyond basic configurations to explore advanced tuning parameters and diagnostic techniques essential for production environments.

Nginx Configuration for High Throughput

Nginx acts as the front-line defense, handling SSL termination, static file serving, request buffering, and load balancing. Proper tuning here is paramount.

Worker Processes and Connections

The worker_processes directive dictates how many worker processes Nginx will spawn. Setting this to auto is often a good starting point, allowing Nginx to detect the number of CPU cores. For I/O-bound workloads, increasing worker_connections can be beneficial, but it’s limited by the system’s file descriptor limits.

Tuning worker_processes and worker_connections

Edit your main Nginx configuration file (typically /etc/nginx/nginx.conf).

Example nginx.conf Snippet

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; # Adjust based on system limits and expected load
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    server_tokens off; # Hide Nginx version for security

    # Gzip compression for text-based assets
    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;

    # Buffering and timeouts for upstream connections
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    # Include virtual host configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Checking File Descriptor Limits

Ensure your system’s file descriptor limits are sufficient. You can check the current limits with ulimit -n and increase them by editing /etc/security/limits.conf.

# Add these lines to /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
root soft nofile 65536
root hard nofile 65536

# Also, configure systemd for Nginx if applicable
# Edit /etc/systemd/system/nginx.service.d/override.conf (create if it doesn't exist)
# [Service]
# LimitNOFILE=65536

After modifying limits.conf, you’ll need to log out and log back in, or restart the Nginx service for changes to take effect. For systemd services, a daemon-reload and service restart is required.

sudo systemctl daemon-reload
sudo systemctl restart nginx

Request Buffering and Timeouts

Nginx buffers client requests. Large buffers can consume memory but prevent slow clients from hogging resources. Tune proxy_buffer_size, proxy_buffers, and proxy_busy_buffers_size to match your application’s typical request sizes and upstream server capabilities.

Gzip Compression

Enabling gzip compression significantly reduces bandwidth usage and improves perceived load times for text-based assets. The configuration above includes robust gzip settings.

Gunicorn Tuning for Ruby Applications

Gunicorn is a popular WSGI HTTP Server for Python-based applications, commonly used with frameworks like Django and Flask. If your Ruby application is served via a WSGI interface (e.g., using `rack-wsgi`), Gunicorn can be a viable option. For typical Rails/Sinatra applications, Puma or Unicorn are more idiomatic. However, if Gunicorn is your choice, here’s how to tune it.

Worker Processes and Threads

Gunicorn’s concurrency model is based on worker processes and, within those processes, threads. The optimal balance depends heavily on your application’s I/O vs. CPU-bound nature and the number of CPU cores available.

Worker Types

  • Sync Workers (sync): The default. Each worker handles one request at a time. Simple but can block under heavy load.
  • Asynchronous Workers (eventlet, gevent): Use non-blocking I/O. Can handle many concurrent connections per worker. Requires monkey patching.
  • Threaded Workers (gthread): Uses threads within a worker process. Good for I/O-bound tasks but subject to Python’s Global Interpreter Lock (GIL) for CPU-bound tasks.

Tuning Strategy

A common starting point for sync workers is (2 * number_of_cores) + 1. For threaded workers, you might use fewer worker processes but more threads per worker. Asynchronous workers can often handle a much higher number of concurrent connections per worker.

Example Gunicorn Command Line / Systemd Service

# Example for a Rails app using Rack::Handler::Puma (if you were using Puma directly)
# For Gunicorn with a WSGI app:
# gunicorn --workers 4 --threads 2 --worker-class sync myapp.wsgi:application --bind 0.0.0.0:8000

# Systemd service file example (/etc/systemd/system/myapp.service)
[Unit]
Description=Gunicorn instance to serve myapp
After=network.target

[Service]
User=myappuser
Group=myappgroup
WorkingDirectory=/home/myappuser/myapp
Environment="PATH=/home/myappuser/myapp/venv/bin"
ExecStart=/home/myappuser/myapp/venv/bin/gunicorn \
    --workers 4 \
    --threads 2 \
    --worker-class sync \
    --bind unix:/home/myappuser/myapp/myapp.sock \
    --timeout 120 \
    --log-level info \
    --access-logfile /var/log/myapp/access.log \
    --error-logfile /var/log/myapp/error.log \
    myapp.wsgi:application

[Install]
[Install]
WantedBy=multi-user.target

Note: The example above uses a Unix socket for communication with Nginx, which is generally more performant than TCP/IP for local communication. Adjust --workers, --threads, and --worker-class based on your application’s profile and Linode instance size.

PHP-FPM Tuning (If Applicable)

While less common for Ruby applications, if you’re running a mixed environment or a Ruby-based PHP application, PHP-FPM is the standard. Tuning PHP-FPM is crucial for performance.

Process Management Modes

  • Static: Pre-forks a fixed number of child processes. Predictable resource usage, good for stable loads.
  • Dynamic: Starts with a few processes and spawns more as needed, up to a limit. Balances resource usage and responsiveness.
  • On-Demand: Starts with one process and spawns more as requests come in. Can save resources but may have higher initial latency.

Tuning Parameters

Edit your PHP-FPM pool configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf).

Example www.conf Snippet (Dynamic Mode)

; /etc/php/8.1/fpm/pool.d/www.conf

[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock ; Or a TCP port like 127.0.0.1:9000
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 50       ; Max number of children at any one time
pm.start_servers = 5       ; Number of servers at boot time
pm.min_spare_servers = 2   ; Min number of idle servers
pm.max_spare_servers = 10  ; Max number of idle servers
pm.process_idle_timeout = 10s ; How long to keep idle processes alive

; For static mode:
; pm = static
; pm.max_children = 50

; For on-demand mode:
; pm = ondemand
; pm.max_children = 50
; pm.process_idle_timeout = 10s

request_terminate_timeout = 120s ; Max execution time for a script
; request_slowlog_timeout = 10s ; Enable slow log if needed
; slowlog = /var/log/php/php8.1-fpm-slow.log

; Other useful settings
; php_admin_value[memory_limit] = 256M
; php_admin_value[upload_max_filesize] = 64M
; php_admin_value[post_max_size] = 64M

Tuning Strategy: Start with pm.max_children based on your Linode’s RAM. A rough guideline is (Total RAM - OS/Other Services RAM) / Average Process Memory Usage. Monitor memory usage and adjust. For dynamic mode, pm.start_servers, pm.min_spare_servers, and pm.max_spare_servers should be set to provide a good balance between responsiveness and resource conservation.

Redis Performance Tuning

Redis is invaluable for caching, session storage, and message queuing. Optimizing its configuration can yield significant performance gains.

Memory Management

The maxmemory directive is crucial to prevent Redis from consuming all available RAM. Setting an appropriate value and a suitable maxmemory-policy ensures Redis behaves predictably under memory pressure.

Tuning maxmemory and maxmemory-policy

Edit your redis.conf file (typically /etc/redis/redis.conf).

# /etc/redis/redis.conf

# Set a memory limit (e.g., 75% of available RAM for Redis)
# Example for a 4GB RAM instance:
maxmemory 3gb

# Choose a policy for evicting keys when maxmemory is reached
# - volatile-lru: Evicts keys with an expire set, least recently used.
# - allkeys-lru: Evicts any key, least recently used.
# - volatile-random: Evicts keys with an expire set, random.
# - allkeys-random: Evicts any key, random.
# - volatile-ttl: Evicts keys with an expire set, shortest time-to-live first.
# - noeviction: Returns errors on write operations when memory limit is reached.
maxmemory-policy allkeys-lru

Tuning Strategy: Allocate a significant portion of your Linode’s RAM to Redis if it’s heavily used for caching/sessions, but leave enough for the OS and your application server. allkeys-lru is a common and effective policy for general-purpose caching.

Persistence

Redis offers RDB snapshots and AOF (Append Only File) logging for persistence. For performance-critical applications where occasional data loss during a crash is acceptable (e.g., cache), disabling or reducing the frequency of persistence can improve write performance.

Disabling or Tuning Persistence

# /etc/redis/redis.conf

# Disable RDB snapshots (if not needed)
save ""

# Or, reduce frequency if RDB is desired but performance is key
# save 900 1    ; Save if 1 key changed in 15 minutes
# save 300 10   ; Save if 10 keys changed in 5 minutes
# save 60 10000 ; Save if 10000 keys changed in 1 minute

# Disable AOF (if not needed)
appendonly no

# If AOF is enabled, tune fsync policy
# appendfsync everysec ; Default, good balance
# appendfsync always   ; Safest, but slowest
# appendfsync no       ; Fastest, but least safe

Tuning Strategy: If Redis is purely for caching and sessions that can be regenerated, disabling persistence entirely offers the best write performance. If some level of durability is required, use appendfsync everysec and consider less frequent RDB snapshots.

Client Connections

The maxclients directive limits the number of concurrent client connections. Ensure this is set high enough to accommodate your application’s needs, but not so high that it exhausts system resources.

# /etc/redis/redis.conf

# Set a reasonable limit for concurrent clients
# Default is 10000, which is often fine. Adjust if you encounter connection issues.
maxclients 20000

Monitoring and Diagnostics

Tuning is an iterative process. Continuous monitoring is essential to validate changes and identify bottlenecks.

Nginx Monitoring

Use Nginx’s status module or tools like netstat and htop.

# Check active connections
sudo ss -tn state established '( sport = :80 or sport = :443 )' | wc -l

# Check worker process status (requires stub_status module enabled in Nginx config)
# Example Nginx config snippet:
# location /nginx_status {
#     stub_status;
#     allow 127.0.0.1;
#     deny all;
# }
curl http://localhost/nginx_status

# Look for:
# Active connections: 1234
# server accepts handled requests
#  1604648414 1604648414 1000000000
# Reading: 6 Writing: 10 Waiting: 1000
# High 'Waiting' count might indicate upstream issues or slow clients.

Gunicorn/PHP-FPM Monitoring

Check process counts and resource utilization.

# For Gunicorn:
ps aux | grep gunicorn

# For PHP-FPM:
sudo systemctl status php8.1-fpm # Check service status
sudo pmap -x $(pgrep php-fpm) | tail -n 1 # Check memory usage per process
# Or use FPM's status page if enabled.

# Check logs for errors or slow requests
tail -f /var/log/myapp/error.log
tail -f /var/log/php/php8.1-fpm-slow.log

Redis Monitoring

Use the redis-cli for real-time insights.

redis-cli
# Inside redis-cli:
INFO memory
# Look for:
# used_memory:34567890
# used_memory_human:32.97M
# maxmemory:3221225472
# maxmemory_human:3.00G
# mem_fragmentation_ratio:1.05

INFO stats
# Look for:
# total_connections_received:12345678
# connected_clients:50
# rejected_connections:0
# expired_keys:1000
# evicted_keys:500

INFO persistence
# Check RDB and AOF status

MONITOR # (Use with caution in production - logs all commands)

High memory fragmentation ratio (significantly above 1.0) might indicate memory allocation issues or require Redis restarts. Zero rejected connections and a reasonable number of connected clients are good signs.

Conclusion

Optimizing Nginx, your application server (Gunicorn/PHP-FPM), and Redis is a multi-faceted task. By systematically tuning these components based on your application’s specific workload and monitoring their performance, you can achieve significant improvements in speed, scalability, and resource utilization on your Linode infrastructure.

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