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

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

Nginx Configuration for High-Traffic WordPress on OVH

Optimizing Nginx is paramount for serving high-traffic WordPress sites. On OVH infrastructure, leveraging its robust network and dedicated resources requires a fine-tuned Nginx configuration. We’ll focus on caching, connection management, and static file serving.

Nginx Caching Strategies

Browser caching and server-side caching are distinct but complementary. For browser caching, we leverage `Expires` and `Cache-Control` headers. For server-side, Nginx’s FastCGI cache is highly effective for dynamic content generated by PHP.

Browser Caching Headers

This configuration snippet should be placed within your WordPress `server` block, typically targeting static assets.

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ {
    expires 365d;
    add_header Cache-Control "public, no-transform";
    access_log off;
    log_not_found off;
}

Here, we set a long expiration for static assets (365 days) and instruct browsers to cache them publicly. `no-transform` prevents intermediaries from modifying the content. Disabling access logs for these assets reduces I/O overhead.

Nginx FastCGI Caching

FastCGI caching stores the output of PHP scripts, significantly reducing the load on your PHP-FPM workers and database. First, define the cache zone in the `http` block:

http {
    # ... other http directives ...

    fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wp_cache:100m inactive=60m max_size=10g;
    fastcgi_temp_path /var/tmp/nginx/fastcgi_temp;

    # ... other http directives ...
}

Explanation:

  • fastcgi_cache_path: Defines the directory for cache files, cache levels, and zone parameters.
  • /var/cache/nginx/wordpress: The directory where cache files will be stored. Ensure this directory exists and Nginx has write permissions.
  • levels=1:2: Sets up a two-level directory structure for cache files to prevent too many files in a single directory.
  • keys_zone=wp_cache:100m: Creates a shared memory zone named wp_cache with 100MB capacity to store cache keys. Adjust size based on expected cache hits.
  • inactive=60m: Items not accessed for 60 minutes will be removed.
  • max_size=10g: The maximum size of the cache on disk.
  • fastcgi_temp_path: A temporary directory for FastCGI operations.

Next, configure your WordPress `location` block to utilize this cache. This typically involves setting cache keys, enabling the cache, and defining cache bypass conditions.

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust to your PHP-FPM version and socket path

    # FastCGI Cache Configuration
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
    fastcgi_cache_valid 404 1m;      # Cache 404s for 1 minute
    fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
    fastcgi_cache_lock on;
    fastcgi_cache_lock_timeout 5s;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;

    # Add cache status header for debugging
    add_header X-Cache-Status $upstream_cache_status;

    # Bypass cache for logged-in users or specific URIs
    set $skip_cache 0;
    if ($http_cookie ~* "wordpress_logged_in|comment_author") {
        set $skip_cache 1;
    }
    if ($request_uri ~* "/wp-admin/|/wp-login.php|/xmlrpc.php|/feed/") {
        set $skip_cache 1;
    }
    if ($request_method = POST) {
        set $skip_cache 1;
    }
}

Key directives:

  • fastcgi_cache_key: Defines how cache entries are identified. Including scheme, method, host, and URI is standard.
  • fastcgi_cache_valid: Specifies how long to cache different HTTP status codes.
  • fastcgi_cache_use_stale: Allows Nginx to serve stale cache content if the backend is unavailable or slow.
  • fastcgi_cache_lock: Prevents multiple requests for the same uncached resource from hitting the backend simultaneously.
  • fastcgi_cache_bypass and fastcgi_no_cache: Use variables to control when the cache is bypassed.
  • $skip_cache: A variable set to 1 to bypass the cache. We set it to 1 for logged-in users (detected via cookies), admin areas, login pages, XML-RPC, and POST requests.
  • X-Cache-Status: A custom header to see if content is served from cache (HIT, MISS, BYPASS, EXPIRED, etc.). Essential for debugging.

Nginx Connection Tuning

Optimizing worker processes and connection limits is crucial for handling concurrent users. These directives are placed in the `http` block.

http {
    # ... other http directives ...

    worker_processes auto; # Or set to the number of CPU cores
    worker_connections 4096; # Adjust based on server memory and expected load
    multi_accept on;

    keepalive_timeout 65;
    keepalive_requests 1000;

    # ... other http directives ...
}

Tuning:

  • worker_processes auto: Nginx will automatically determine the number of worker processes based on the number of CPU cores. This is generally a good starting point.
  • worker_connections: The maximum number of simultaneous connections that each worker process can handle. The total maximum connections is worker_processes * worker_connections. This value should be set considering available RAM and expected load. A common starting point is 4096.
  • multi_accept on: Allows a worker process to accept multiple new connections at once.
  • keepalive_timeout: The time a persistent connection will remain open.
  • keepalive_requests: The maximum number of requests that can be made over a single persistent connection.

Gunicorn Configuration for WordPress (via WSGI)

While WordPress is traditionally PHP-based, using Gunicorn with a WSGI application (like wordpress-wsgi or a custom handler) is an advanced setup for specific use cases, often involving Python-based plugins or custom logic. For standard WordPress, PHP-FPM is the norm. However, if you are in a mixed environment or have specific Python integration needs, here’s a Gunicorn tuning guide.

Gunicorn Worker Processes and Threads

The number of worker processes and threads significantly impacts concurrency. A common strategy is to use a mix of worker types.

# Example command line for starting Gunicorn
gunicorn --workers 3 --threads 2 --bind 0.0.0.0:8000 wsgi:app

Explanation:

  • --workers: The number of worker processes. A common recommendation is (2 * number_of_cpu_cores) + 1.
  • --threads: The number of threads per worker process. This is applicable for gthread or uvicorn workers.
  • --bind: The address and port Gunicorn listens on.
  • wsgi:app: Assumes your WSGI application is named app and is in a file named wsgi.py.

For CPU-bound tasks, more workers are beneficial. For I/O-bound tasks, threads can improve concurrency within a worker. For WordPress, which is largely I/O bound (database, file system), a higher thread count might be considered, but always test thoroughly.

Gunicorn Timeouts and Keepalive

Setting appropriate timeouts prevents hung requests from blocking workers.

# Example command line for starting Gunicorn
gunicorn --workers 3 --threads 2 --bind 0.0.0.0:8000 --timeout 30 --keep-alive 5 wsgi:app

Tuning:

  • --timeout: The number of seconds to wait for the application to respond. If the application takes longer, Gunicorn will kill the worker and restart it. Default is 30 seconds. Adjust based on expected request processing times.
  • --keep-alive: The number of seconds to wait for a request on a keep-alive connection.

PHP-FPM Tuning for WordPress

For standard WordPress deployments on OVH, PHP-FPM is the engine. Tuning its process manager and memory limits is critical.

PHP-FPM Process Manager Settings

These settings are typically found in your PHP-FPM pool configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf).

; For a server with 4 CPU cores and 16GB RAM, a good starting point:
pm = dynamic
pm.max_children = 100
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.process_idle_timeout = 10s
pm.max_requests = 500

Explanation:

  • pm = dynamic: The process manager. dynamic is often preferred as it scales workers based on load. static keeps a fixed number of workers running.
  • pm.max_children: The maximum number of child processes that can be spawned. This is the most critical setting for memory usage. Set this based on your server’s available RAM. Calculate: (Total RAM - RAM for OS/other services) / Average RAM per PHP-FPM process.
  • pm.start_servers: The number of child processes started when the pool starts.
  • pm.min_spare_servers: The minimum number of idle processes that should be kept waiting.
  • pm.max_spare_servers: The maximum number of idle processes that can be kept waiting.
  • pm.process_idle_timeout: The number of seconds after which an idle process will be killed.
  • pm.max_requests: The number of requests each child process will execute before respawning. This helps prevent memory leaks.

PHP Memory Limits

WordPress and its plugins can be memory-intensive. Adjusting memory_limit in php.ini is essential.

; In /etc/php/8.1/fpm/php.ini
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120

Tuning:

  • memory_limit: The maximum amount of memory a script can consume. 256MB is a good starting point for WordPress.
  • upload_max_filesize and post_max_size: Important for handling media uploads. Ensure post_max_size is greater than or equal to upload_max_filesize.
  • max_execution_time: The maximum time a script is allowed to run before it’s terminated. Essential for long-running tasks like imports or complex queries.

Remember to restart PHP-FPM after making changes: sudo systemctl restart php8.1-fpm.

MySQL Tuning for WordPress on OVH

Database performance is often the bottleneck. Tuning MySQL’s configuration file (my.cnf or files in /etc/mysql/mysql.conf.d/) is crucial.

InnoDB Buffer Pool

The InnoDB buffer pool is where InnoDB caches table and index data. This is the single most important setting for InnoDB performance.

[mysqld]
innodb_buffer_pool_size = 4G  ; Adjust based on available RAM (e.g., 50-75% of dedicated RAM)
innodb_buffer_pool_instances = 4 ; Number of buffer pool instances (typically 1 per GB of buffer pool size)

Tuning:

  • innodb_buffer_pool_size: For a dedicated MySQL server on OVH, allocating 4GB to 8GB (or more, depending on instance type) is common. Monitor Innodb_buffer_pool_read_requests vs. Innodb_buffer_pool_reads. A hit rate above 99% is desirable.
  • innodb_buffer_pool_instances: Helps reduce contention on the buffer pool mutex. Set to the number of CPU cores or 1 per GB of buffer pool size.

Connection and Thread Handling

Managing client connections and query threads efficiently.

[mysqld]
max_connections = 200 ; Adjust based on expected concurrent users and application behavior
thread_cache_size = 16  ; Cache threads for reuse

Tuning:

  • max_connections: The maximum number of simultaneous client connections. Too low will cause connection errors; too high can exhaust server resources. Monitor Max_used_connections status variable.
  • thread_cache_size: How many idle threads to keep in cache. A value of 16 or 32 is often sufficient.

Query Cache (Deprecated but relevant for older MySQL versions)

Note: The query cache is deprecated in MySQL 5.7 and removed in MySQL 8.0. If you are on an older version, it *might* offer a benefit, but often causes more contention than it solves. For modern deployments, ignore this.

; [mysqld]
; query_cache_type = 1
; query_cache_size = 64M
; query_cache_limit = 1M

If used, monitor Qcache_hits vs. Qcache_inserts and Qcache_lowmem_prunes. High prunes indicate the cache is too small or too many queries are invalidating it.

Logging and Slow Queries

Identifying slow queries is crucial for optimization. Enable the slow query log.

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2 ; Log queries taking longer than 2 seconds
log_queries_not_using_indexes = 1 ; Log queries that don't use indexes

Regularly analyze the mysql-slow.log using tools like pt-query-digest to pinpoint problematic SQL statements. Optimize these queries or add appropriate indexes.

OVH Specific Considerations

OVH’s infrastructure often provides dedicated resources, which simplifies some tuning by allowing more aggressive allocation. However, always monitor resource utilization (CPU, RAM, I/O, Network) using OVH’s control panel and standard Linux tools (htop, iotop, nload) to validate your tuning decisions.

For database performance, consider using OVH’s managed database services if available, as they often come pre-tuned and offer high availability. If self-hosting MySQL on an OVH instance, ensure your instance type has sufficient IOPS for your workload.

Network latency can be a factor. Ensure your Nginx and PHP-FPM configurations are optimized to minimize round trips and leverage keep-alive connections effectively.

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