• 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 OVH for PHP

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and Redis on OVH for PHP

Nginx as a High-Performance Frontend for PHP Applications

When deploying PHP applications, especially those leveraging modern frameworks and APIs, Nginx serves as an exceptionally efficient frontend. Its asynchronous, event-driven architecture excels at handling a high volume of concurrent connections, offloading the heavy lifting from your application servers. This section details crucial Nginx tuning parameters for optimal PHP performance on OVH infrastructure.

Core Nginx Configuration Tuning

The primary configuration file, typically located at /etc/nginx/nginx.conf, contains global directives that influence worker processes and connection handling. For production environments, especially on OVH’s robust network, we can push these limits further.

Worker Processes and Connections

The worker_processes directive should ideally be set to the number of CPU cores available on your server. This allows Nginx to utilize all available processing power. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can handle. The total maximum connections will be worker_processes * worker_connections.

Consider the following snippet for /etc/nginx/nginx.conf:

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 expected load and server RAM
    multi_accept on;
}

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

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

    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m; # Adjust size based on traffic
    ssl_session_timeout 10m;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Note: On OVH, especially with dedicated servers, you have direct access to hardware. Use lscpu to determine the number of CPU cores and adjust worker_processes accordingly. For worker_connections, monitor your server’s memory usage. A value of 4096 is a good starting point, but can be increased if RAM permits and load demands it.

Optimizing PHP-FPM/Gunicorn Configuration

Whether you’re using PHP-FPM for traditional PHP or Gunicorn for Python/WSGI applications, the principles of process management and resource allocation are similar. These application servers are the workhorses that execute your PHP/Python code.

PHP-FPM Tuning

The PHP-FPM configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf) is critical. The pm (process manager) setting dictates how FPM manages child processes. For production, ondemand or dynamic are generally preferred over static, as they scale based on demand, conserving resources during low traffic periods.

Process Manager Settings

Here’s a sample configuration for www.conf using the dynamic process manager:

; Start a new child until the pm.max_children limit is reached.
; pm = dynamic

; The number of child processes that will be used when pm = dynamic.
; pm.max_children = 50 ; Adjust based on available RAM and CPU cores. A good starting point is (total_ram_mb / avg_process_size_mb) / num_cores.

; The number of *additional* child processes which will be spawned when the following requests are executed.
; pm.start_servers = 2

; The minimum number of children that should always be running.
; pm.min_spare_servers = 1

; The maximum number of children that should always be running.
; pm.max_spare_servers = 5

; The number of requests each child process should execute before respawning.
; This helps to prevent memory leaks.
pm.max_requests = 500

; The timeout for serving a request.
request_terminate_timeout = 60 ; seconds

; The timeout for acquiring a child from the pool.
pm.process_idle_timeout = 10s ; seconds

Tuning Strategy:

  • pm.max_children: This is the most critical parameter. Calculate it by estimating the average memory footprint of a PHP-FPM worker process (e.g., using ps aux --sort=-%mem | grep php-fpm during peak load) and dividing your server’s available RAM by this figure. Then, consider your CPU cores. A common heuristic is to aim for a total number of children that your CPUs can reasonably handle concurrently. Start conservatively and monitor.
  • pm.start_servers, pm.min_spare_servers, pm.max_spare_servers: These control the dynamic scaling. Adjust them to ensure a quick response to traffic spikes without over-provisioning.
  • pm.max_requests: Essential for long-running applications to mitigate memory leaks. 500 is a common value; adjust based on application stability.
  • request_terminate_timeout: Set this to a reasonable value (e.g., 60 seconds) to prevent hung requests from holding up worker processes indefinitely.

Gunicorn Tuning (for Python/WSGI)

Gunicorn’s worker class and number of workers are key. The sync worker class is simple but blocking. For I/O-bound applications, gevent or eventlet (asynchronous workers) are highly recommended. The number of workers is typically set to (2 * number_of_cores) + 1 as a starting point.

Example Gunicorn command line:

gunicorn --workers 3 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application

Tuning Strategy:

  • --workers: Start with (2 * CPU cores) + 1. Monitor CPU and memory usage.
  • --worker-class: For I/O-bound tasks (API calls, database queries), gevent or eventlet are superior to sync. Ensure you have the necessary libraries installed (e.g., pip install gevent).
  • --bind: This should typically bind to a local interface (e.g., 127.0.0.1:8000) and be proxied by Nginx.
  • --threads: If using a synchronous worker class (like sync), you can use threads to handle concurrency within a worker. However, for I/O-bound tasks, asynchronous workers are generally more efficient.

Redis as a High-Performance Cache and Session Store

Redis is an indispensable tool for reducing database load and speeding up application response times. Proper configuration on OVH servers ensures it’s a reliable and fast component of your stack.

Redis Configuration Tuning (redis.conf)

The main configuration file is typically /etc/redis/redis.conf. Key parameters to focus on include memory management, persistence, and network settings.

Memory Management

maxmemory is crucial to prevent Redis from consuming all available RAM. Set this to a value that leaves ample memory for the OS and other services. maxmemory-policy determines how Redis evicts keys when maxmemory is reached.

# Set a limit on memory usage
maxmemory 2gb ; Adjust based on server RAM and other services. Leave at least 20-30% for OS.

# When maxmemory limit is reached, Redis will try to evict a key based on the policy.
# allkeys-lru: Evict using an approximation of Least Recently Used (LRU) algorithm. Good for general caching.
# volatile-lru: Evict only keys with an expire set, using LRU.
# allkeys-random: Evict a random key.
# volatile-random: Evict a random key with an expire set.
# volatile-ttl: Evict keys with an expire set, prioritizing those with shortest TTL.
maxmemory-policy allkeys-lru
Persistence

For caching scenarios where data loss is acceptable, disabling or minimizing persistence can improve performance. If Redis is used for critical data or session storage, configure RDB snapshots and/or AOF logging appropriately.

# Disable RDB snapshots if Redis is purely for caching and data loss is acceptable.
# save ""

# If using RDB, adjust the save points to balance performance and data safety.
# save 900 1
# save 300 10
# save 60 10000

# Append Only File (AOF) logging. More durable but can impact write performance.
# appendonly no ; Set to 'yes' for higher durability.

# AOF fsync policy:
# everysec: fsync every second (default). Good balance.
# always: fsync after every write command. Slowest but safest.
# no: Never fsync. Relies on OS. Fastest but least safe.
# appendfsync everysec

Recommendation for Caching: If Redis is solely for caching, consider disabling RDB snapshots (save "") and setting appendonly no. This significantly reduces disk I/O and improves performance. If it’s for sessions or critical data, use appendonly yes with appendfsync everysec and appropriate RDB save points.

Network and Performance

Tuning tcp-backlog can help handle bursts of new connections. Ensure Redis is bound to a specific IP address for security, especially if it’s not directly exposed to the internet.

# Set the TCP backlog setting.
# tcp-backlog 511 ; Default is 511. Can be increased on busy servers.

# Bind to a specific IP address for security.
# bind 127.0.0.1 ; Or the private IP of your OVH server if accessed from other internal machines.
# bind 192.168.1.100 127.0.0.1

Putting It All Together: Nginx, PHP-FPM/Gunicorn, and Redis Integration

The synergy between these components is where true performance gains are realized. Nginx acts as the gatekeeper, efficiently passing requests to PHP-FPM/Gunicorn, which then leverages Redis for fast data retrieval and storage.

Nginx to PHP-FPM/Gunicorn Proxy Configuration

Your Nginx site configuration (e.g., /etc/nginx/sites-available/your-app) needs to be set up to proxy requests correctly. For PHP-FPM, this involves FastCGI. For Gunicorn, it’s HTTP proxying.

Nginx with PHP-FPM Example
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    root /var/www/your-app/public;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Make sure this matches your PHP-FPM pool socket or address:port
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny all;
    }

    # Caching static assets with Nginx
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public";
        access_log off;
    }
}
Nginx with Gunicorn Example
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:8000; # Assuming Gunicorn is bound to 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;
    }

    # Caching static assets with Nginx
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public";
        access_log off;
    }
}

Integrating Redis for Caching and Sessions

In your PHP application (e.g., using Symfony, Laravel, or custom code), configure your cache and session handlers to use Redis. This typically involves installing a Redis client library (like phpredis or Predis) and updating your application’s configuration.

Example (Conceptual PHP):

<?php
// Assuming you have a Redis client library installed and configured

// For caching
$redis = new Redis();
$redis->connect('127.0.0.1', 6379); // Connect to your Redis instance

// Example: Storing and retrieving a cached item
$cacheKey = 'user_data_' . $userId;
$cachedData = $redis->get($cacheKey);

if ($cachedData === false) {
    // Data not in cache, fetch from DB
    $data = fetchUserDataFromDatabase($userId);
    // Store in Redis with an expiration time (e.g., 1 hour)
    $redis->setex($cacheKey, 3600, json_encode($data));
} else {
    $data = json_decode($cachedData, true);
}

// For sessions (e.g., in Symfony's config/packages/session.yaml)
// framework:
//     session:
//         handler_id: session.storage.redis
//         save_path: 'tcp://127.0.0.1:6379?timeout=2.5&read_timeout=10'

// Or using Predis
// use Predis\Client;
// $redis = new Client([
//     'scheme' => 'tcp',
//     'host'   => '127.0.0.1',
//     'port'   => 6379,
// ]);
// $redis->set('my_key', 'my_value', 'ex', 3600); // Set with expiration
?>

Monitoring and Diagnostics

Continuous monitoring is key to identifying bottlenecks and ensuring your tuning efforts are effective. Utilize tools like:

  • Nginx: nginx -s reload for configuration changes, /var/log/nginx/access.log and error.log, and Nginx status module.
  • PHP-FPM: systemctl status php8.1-fpm, PHP-FPM logs (often in /var/log/php8.1-fpm.log), and the FPM status page.
  • Gunicorn: Application logs, and process monitoring tools like htop or supervisorctl status.
  • Redis: redis-cli INFO memory, redis-cli INFO stats, redis-cli slowlog get 10, and redis-cli MONITOR (use with caution in production).
  • System: top, htop, vmstat, iostat, and netstat are invaluable for understanding overall system resource utilization.

On OVH, leverage their monitoring tools as well, particularly for network traffic and hardware health. Regularly review these metrics to fine-tune your configurations further.

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 indexing lock conflicts and high CPU during bulk stock updates on DigitalOcean Servers
  • How to Debug and Fix memory leaks and socket exhaustion in daemon processes in Modern C++ Applications
  • Infrastructure as Code: Provisioning Secure PHP Clusters on DigitalOcean Using Terraform
  • Fixing Slow Largest Contentful Paint (LCP) caused by unoptimized database queries in Legacy Laravel Codebases Without Breaking API Contracts
  • An Auditor’s Checklist for Securing Laravel Backends on Google Cloud

Copyright © 2026 · Vinay Vengala