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

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

Optimizing Nginx as a Reverse Proxy for PHP Applications

When deploying PHP applications, particularly those leveraging modern frameworks and asynchronous task processing, Nginx serves as an indispensable reverse proxy. Its efficiency in handling static assets and buffering client requests is paramount. This section details critical Nginx tuning parameters for a high-performance PHP stack on OVH infrastructure.

Nginx Configuration Tuning

The primary configuration file for Nginx is typically located at /etc/nginx/nginx.conf. We’ll focus on tuning the http block and individual server blocks.

Global HTTP Settings

Within the http block, several directives significantly impact performance and stability:

  • worker_processes: Set this to the number of CPU cores available. On OVH instances, this can be determined using nproc or lscpu.
  • worker_connections: This defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024 or higher, depending on expected load. The system-wide limit (ulimit -n) must be higher than worker_processes * worker_connections.
  • multi_accept: Setting this to on allows workers to accept as many new connections as possible in one go.
  • keepalive_timeout: Reduces the overhead of establishing new TCP connections. A value like 65 seconds is often suitable.
  • send_timeout: Controls how long Nginx will wait for a response from the client before closing the connection. A value of 10 seconds is a reasonable default.
  • client_body_buffer_size: Important for handling POST requests. A value of 128k or 256k is often sufficient.
  • client_header_buffer_size: For client headers. 1k is usually fine, but 2k can be safer for complex headers.
  • large_client_header_buffers: For large client headers. 4 8k is a common setting.
  • open_file_cache: Caches file descriptors and metadata, reducing disk I/O.

Here’s an example snippet for the http block:

http {
    # Determine worker_processes dynamically or set based on CPU cores
    # Example: worker_processes auto; or worker_processes 4;
    worker_processes 4;
    worker_connections 4096; # Ensure ulimit -n is set higher
    multi_accept on;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65;
    keepalive_requests 1000;

    client_body_buffer_size 256k;
    client_header_buffer_size 2k;
    large_client_header_buffers 4 8k;

    # Enable open file cache for performance
    open_file_cache max=2000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Include mime types and other configurations
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # ... other http configurations
}

Server Block Configuration for PHP-FPM

For PHP applications, Nginx acts as a proxy to PHP-FPM. The location ~ \.php$ block is critical. Ensure it’s configured for optimal communication with your PHP-FPM pool.

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;

    # Deny access to hidden files
    location ~ /\.ht {
        deny all;
    }

    # Pass PHP scripts to PHP-FPM
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # Use the correct socket or IP:Port for your PHP-FPM pool
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Or 127.0.0.1:9000
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # FastCGI buffer tuning
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        fastcgi_read_timeout 300s; # Adjust based on long-running scripts
    }

    # Serve static files directly
    location ~* \.(css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }

    # Other configurations (e.g., SSL, error pages)
    # ...
}

Nginx Performance Checks

After applying changes, test your Nginx configuration and reload the service:

sudo nginx -t
sudo systemctl reload nginx

Monitor Nginx access and error logs (/var/log/nginx/access.log, /var/log/nginx/error.log) for any issues. Tools like ab (ApacheBench) or wrk can be used for load testing.

Tuning Gunicorn for PHP (via WSGI/ASGI Wrappers)

While Gunicorn is primarily a Python WSGI/ASGI HTTP Server, it can be used to serve PHP applications through wrappers like php-fpm-wsgi or similar solutions. This approach is less common than direct PHP-FPM but offers flexibility in environments where Python tooling is preferred. The tuning principles for Gunicorn itself remain relevant.

Gunicorn Configuration Parameters

Gunicorn’s performance is dictated by its worker processes, threads, and timeouts. A typical Gunicorn configuration file (e.g., gunicorn_config.py) might look like this:

# gunicorn_config.py
import multiprocessing

# Number of worker processes. A common recommendation is (2 * number_of_cores) + 1.
workers = multiprocessing.cpu_count() * 2 + 1

# Worker class. 'sync' is the default and most basic. 'gevent' or 'eventlet'
# can be used for asynchronous I/O if your application supports it.
worker_class = 'sync' # or 'gevent', 'eventlet'

# Number of threads per worker (only applicable for 'gthread' worker class)
# threads = 2

# Bind to a socket or IP:Port. For Nginx proxying, a Unix socket is often preferred.
# bind = "unix:/path/to/your/app.sock"
bind = "127.0.0.1:8000" # Example for local binding

# Maximum number of simultaneous connections per worker.
# For 'sync' workers, this is typically 1. For 'eventlet'/'gevent', it can be much higher.
# max_requests = 1000 # Restart workers after this many requests
# max_requests_jitter = 50 # Jitter to spread restarts

# Timeout for worker requests.
# Adjust based on the longest expected request processing time.
timeout = 120 # seconds

# Logging configuration
loglevel = 'info'
accesslog = '-' # Log to stdout
errorlog = '-'  # Log to stderr

# Other settings
# pidfile = '/var/run/gunicorn.pid'
# daemon = True
# user = 'www-data'
# group = 'www-data'

To run Gunicorn with this configuration:

gunicorn --config gunicorn_config.py your_wsgi_app:app

If using a PHP-WSGI wrapper, ensure the underlying PHP-FPM configuration is also optimized (see next section).

Tuning PHP-FPM for Optimal Performance

PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with web servers like Nginx. Its configuration directly impacts PHP execution speed and resource utilization.

PHP-FPM Configuration Files

The main configuration file is usually /etc/php/X.Y/fpm/php-fpm.conf, and pool configurations are in /etc/php/X.Y/fpm/pool.d/www.conf (or a custom pool name).

Global Settings (php-fpm.conf)

Key global settings include:

  • pid: Path to the PID file.
  • error_log: Path to the error log.
  • log_level: Logging verbosity (e.g., notice, warning, error, debug).

Pool Settings (pool.d/www.conf)

This is where most tuning occurs. The pm (Process Manager) directive is crucial:

  • pm: Can be static, dynamic, or ondemand.
    • static: Keeps a fixed number of child processes running. Best for predictable high loads.
    • dynamic: Starts processes as needed, up to a maximum. Good for variable loads.
    • ondemand: Starts processes only when a request comes in and kills them after a timeout. Saves memory but can increase latency.
  • pm.max_children: The maximum number of child processes that will be spawned. This is the most critical setting for memory usage.
  • pm.start_servers: Number of child processes to start when PHP-FPM starts.
  • pm.min_spare_servers: Minimum number of idle processes.
  • pm.max_spare_servers: Maximum number of idle processes.
  • pm.process_idle_timeout: How long an idle process will live (used with dynamic).
  • pm.max_requests: Maximum number of requests a child process will execute before respawning. Helps prevent memory leaks.
  • listen: The socket or IP:Port PHP-FPM listens on (must match Nginx configuration).
  • request_terminate_timeout: Maximum execution time for a single script.
  • pm.child_init_timeout: Timeout for child process initialization.

Example pool.d/www.conf for a moderate-traffic server (adjust based on OVH instance RAM and CPU):

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

[www]
user = www-data
group = www-data
listen = /var/run/php/php7.4-fpm.sock ; Or 127.0.0.1:9000
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 50      ; Adjust based on available RAM. (Total RAM / Average Child RAM Usage)
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 10
pm.max_requests = 500     ; Prevents memory leaks over time

request_terminate_timeout = 120s ; Long enough for most operations, but not infinite
pm.child_init_timeout = 120s

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

After modifying PHP-FPM configuration, reload the service:

sudo systemctl reload php7.4-fpm

Monitor PHP-FPM logs (e.g., /var/log/php7.4-fpm.log) and system resource usage (top, htop, free -m) to fine-tune pm.max_children and other parameters.

MySQL/MariaDB Performance Tuning on OVH

Database performance is often the bottleneck. Tuning MySQL/MariaDB involves adjusting configuration parameters and optimizing queries.

MySQL Configuration (my.cnf)

The main configuration file is typically /etc/mysql/my.cnf or located within /etc/mysql/conf.d/. Key parameters to adjust:

  • innodb_buffer_pool_size: The most critical setting for InnoDB. Should be 50-70% of available RAM on a dedicated database server. On a shared server, be more conservative.
  • innodb_log_file_size: Larger log files can improve write performance but increase recovery time. A common starting point is 256M or 512M.
  • innodb_log_buffer_size: Larger buffer can improve performance for large transactions. 16M is often sufficient.
  • max_connections: Maximum number of simultaneous client connections. Set based on application needs and server capacity.
  • query_cache_size and query_cache_type: Query cache is deprecated in MySQL 5.7 and removed in 8.0. If using older versions, tune carefully; it can cause contention. For modern versions, disable it.
  • tmp_table_size and max_heap_table_size: Affects the size of in-memory temporary tables.
  • key_buffer_size: Important for MyISAM tables (less common now).
  • sort_buffer_size, join_buffer_size, read_buffer_size, read_rnd_buffer_size: Per-session buffers. Tune cautiously as they can consume significant memory per connection.

Example my.cnf snippet (for a server with 8GB RAM, primarily a DB server):

[mysqld]
# General Settings
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc_messages = en_US
skip-external-locking

# InnoDB Settings (Crucial for performance)
default_storage_engine = InnoDB
innodb_buffer_pool_size = 4G       ; ~50% of 8GB RAM
innodb_log_file_size = 512M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 1 ; For ACID compliance, 2 for better performance with slight risk
innodb_flush_method = O_DIRECT     ; Recommended for Linux
innodb_file_per_table = 1          ; Recommended for manageability

# Connection Settings
max_connections = 200              ; Adjust based on application needs
# thread_cache_size = 16           ; Cache threads

# Query Cache (Disable for MySQL 8.0+, tune cautiously for older versions)
query_cache_type = 0
query_cache_size = 0

# Per-session Buffers (Tune cautiously)
# sort_buffer_size = 2M
# join_buffer_size = 2M
# read_buffer_size = 1M
# read_rnd_buffer_size = 2M

# MyISAM Settings (if applicable)
# key_buffer_size = 128M

# Logging
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2                ; Log queries longer than 2 seconds
log_queries_not_using_indexes = 1  ; Log queries that don't use indexes

After changes, restart MySQL:

sudo systemctl restart mysql

Query Optimization and Indexing

Even with perfect server tuning, inefficient queries will cripple performance. Regularly analyze slow query logs and use EXPLAIN to understand query execution plans.

-- Example of using EXPLAIN
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';

-- Add an index if 'email' column is frequently queried
CREATE INDEX idx_users_email ON users (email);

OVH provides tools for monitoring database performance within their control panel, which should be leveraged to identify problematic queries and resource bottlenecks.

Putting It All Together: Monitoring and Iteration

Performance tuning is an iterative process. Implement changes incrementally and monitor their impact. Key metrics to track include:

  • Server CPU and RAM usage (top, htop, vmstat)
  • Nginx request rates, error rates, and response times (using ngx_http_stub_status_module or external monitoring tools)
  • PHP-FPM process count and queue length
  • MySQL connection usage, query latency, and buffer pool hit rate
  • Application-specific metrics (e.g., response times for key API endpoints)

Utilize tools like Prometheus, Grafana, Datadog, or New Relic for comprehensive monitoring. On OVH, their built-in monitoring dashboards offer a good starting point.

Remember to always back up your configuration files before making changes and test thoroughly in a staging environment before deploying to production.

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

  • Java Spring Boot vs. Go: Database Connection Pooling and Transaction Latency (p99)
  • Rust vs. Go for Custom Database Drivers: Memory Layout and Raw TCP Socket Handling Performance
  • C# ASP.NET Core vs. Rust Axum: Enterprise ORM Complexity (EF Core) vs. Low-Level Database Access (SQLx)
  • Node.js (TypeScript) vs. Python (FastAPI): Cold Start Mitigation for AWS Lambda Serverless API Gateways
  • Go vs. Rust: Developing Developer-Facing CLI API Client Wrappers with Minimum Binary Footprints

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (583)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (959)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (23)
  • MySQL (1)
  • Performance & Optimization (797)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (6)
  • Python (16)
  • 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

  • Java Spring Boot vs. Go: Database Connection Pooling and Transaction Latency (p99)
  • Rust vs. Go for Custom Database Drivers: Memory Layout and Raw TCP Socket Handling Performance
  • C# ASP.NET Core vs. Rust Axum: Enterprise ORM Complexity (EF Core) vs. Low-Level Database Access (SQLx)
  • Node.js (TypeScript) vs. Python (FastAPI): Cold Start Mitigation for AWS Lambda Serverless API Gateways
  • Go vs. Rust: Developing Developer-Facing CLI API Client Wrappers with Minimum Binary Footprints
  • Ruby on Rails vs. Django vs. Laravel: Comparative Query Optimization and Boot Times in Modern Monoliths

Top Categories

  • DevOps & Cloud Scaling (959)
  • Performance & Optimization (797)
  • Debugging & Troubleshooting (583)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • School Management & Student Administration System
  • Integrated Hospital & Clinic Management System
  • Real Estate Directory & Agent Portal
  • Restaurant POS & Table Booking System
  • Retail Inventory POS & Billing System
  • Pharmacy Inventory & Clinic Billing System

Our Services

  • Vibe Engineering & AI Code Auditing Services
  • Prompt Engineering & "Vibe Coding" Workflow Consulting
  • AI-Augmented "Vibe Coding" & Rapid MVP Development
  • Figma to Shopify Liquid Theme Customization
  • Figma to WooCommerce Frontend Development
  • Figma to Magento 2 Theme Development

Copyright © 2026 · Vinay Vengala