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

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

Nginx Configuration for WordPress High-Traffic Scenarios

Optimizing Nginx for a high-traffic WordPress site involves fine-tuning worker processes, connection limits, caching, and static file serving. On OVH infrastructure, leveraging their robust network and dedicated resources is key. We’ll focus on `nginx.conf` and site-specific configurations.

Tuning Worker Processes and Connections

The `worker_processes` directive should ideally be set to the number of CPU cores available. For dynamic tuning based on load, `auto` can be used, but a fixed number often provides more predictable performance. `worker_connections` dictates the maximum number of simultaneous connections a worker can handle. This needs to be balanced with the system’s `ulimit` settings.

System-Level Limits

Before adjusting Nginx, ensure the operating system’s file descriptor limits are sufficient. Edit `/etc/security/limits.conf` and add or modify these lines (replace `www-data` with your Nginx user if different):

* soft nofile 65536
* hard nofile 65536
www-data soft nofile 65536
www-data hard nofile 65536

Then, edit `/etc/sysctl.conf` to increase the maximum number of open files system-wide:

fs.file-max = 2097152

Apply these changes with `sysctl -p` and verify with `ulimit -n` (for the current shell) and `cat /proc/sys/fs/file-max`.

Nginx `nginx.conf` Tuning

In your main `nginx.conf` (typically `/etc/nginx/nginx.conf`), adjust the `events` block:

user www-data www-data;
worker_processes auto; # Or set to number of CPU cores
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096; # Adjust based on expected load and ulimit
    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;

    # ... other http configurations ...
}

Gunicorn Configuration for WordPress (with WSGI)

While WordPress is traditionally PHP-based, modern architectures might use Gunicorn to serve a Python WSGI application that interacts with WordPress (e.g., via its REST API or a headless CMS setup). If you’re running a PHP application with PHP-FPM, skip this section. For a Python WSGI app:

Gunicorn Worker Types and Scaling

Gunicorn’s performance is heavily influenced by its worker type and the number of workers. For I/O-bound applications (common with web apps), the `gevent` or `event` workers are preferred. The number of workers is typically set to `(2 * number_of_cores) + 1` as a starting point.

Gunicorn Command-Line Options

A typical Gunicorn startup command for a WSGI application (`your_app.wsgi:application`) might look like this:

gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 your_app.wsgi:application

For production, it’s recommended to run Gunicorn under a process manager like `systemd` or `supervisor`. Here’s a sample `systemd` service file (`/etc/systemd/system/gunicorn.service`):

[Unit]
Description=Gunicorn instance to serve your_app
After=network.target

[Service]
User=your_user
Group=your_group
WorkingDirectory=/path/to/your/app
ExecStart=/path/to/your/venv/bin/gunicorn --workers 4 --worker-class gevent --bind unix:/run/gunicorn.sock your_app.wsgi:application
# Or for TCP binding:
# ExecStart=/path/to/your/venv/bin/gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 your_app.wsgi:application

Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

After creating the service file, enable and start it:

sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicorn

PHP-FPM Configuration for WordPress

For standard WordPress deployments, PHP-FPM is the engine. Tuning its process manager is critical. The most common process managers are `static`, `dynamic`, and `ondemand`. For high-traffic sites, `dynamic` or `static` are generally preferred over `ondemand`.

Choosing a Process Manager

  • static: Pre-forks a fixed number of child processes. Best for predictable, high-load environments where memory is not a constraint.
  • dynamic: Starts with a few processes and spawns more as needed, up to a `pm.max_children` limit. Processes are killed if idle. Good balance.
  • ondemand: Spawns processes only when requests arrive. Can save memory but introduces latency for the first request after an idle period. Not ideal for high-traffic, low-latency needs.

PHP-FPM Pool Configuration (`www.conf`)

Locate your PHP-FPM pool configuration file, typically `/etc/php/X.Y/fpm/pool.d/www.conf` (replace X.Y with your PHP version). Here’s a tuned example using the `dynamic` process manager:

; Start a new pool named 'www'.
[www]

; Unix user/group of processes
user = www-data
group = www-data

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'listen = /run/php/phpX.Y-fpm.sock' (for Unix sockets, recommended for performance)
;   'listen = 127.0.0.1:9000' (for TCP/IP sockets)
listen = /run/php/php8.1-fpm.sock ; Adjust PHP version

; Listen queue. Maximum number of the children that can be waiting for processing.
; Triggered when pm.max_children is reached. Default Value: 0
listen.backlog = 511

; Number of child processes to kill after 30 seconds of inactivity.
pm.max_requests = 500 ; Helps prevent memory leaks

; Choose how the process manager (pm) should behave.
; Available values: 'dynamic', 'static', 'ondemand'.
pm = dynamic

; The method to use to start children.
; 'static' means always have this many children.
; 'dynamic' means start with pm.start_servers and grow to pm.max_children.
; 'ondemand' means start no children until first request.
pm.max_children = 150      ; Adjust based on available RAM and CPU cores
pm.min_spare_servers = 10  ; Minimum number of idle servers
pm.max_spare_servers = 30  ; Maximum number of idle servers
pm.start_servers = 5       ; Number of servers to start on boot

; The maximum number of processes that will be spawned.
; Default Value: not set
; pm.max_children = 50 ; If using 'static' pm, this is the fixed number

; If using 'dynamic' pm, this is the number of child processes created on startup.
; Default Value: 0
; pm.start_servers = 2

; If using 'dynamic' pm, this is the desired number of top-level servers.
; Default Value: 5
; pm.min_spare_servers = 1
; pm.max_spare_servers = 3

; If using 'dynamic' pm, the maximum number of seconds a child process may live without
; handling requests. The child process will be killed after this time. The default
; value is 0 (never stop).
; pm.max_requests = 0

; Process Idle Timeout (seconds)
; pm.process_idle_timeout = 10s ; Default is 10s

; Request termination timeout (seconds)
; request_terminate_timeout = 0 ; Default is 0 (no timeout)

; Slowlog
; slowlog = /var/log/php/php8.1-fpm.slow.log
; request_slowlog_timeout = 10s

; 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] = 300

After modifying `www.conf`, restart PHP-FPM:

sudo systemctl restart php8.1-fpm ; Adjust PHP version

Elasticsearch Tuning for WordPress Search

Integrating Elasticsearch with WordPress, often via plugins like “ElasticPress,” significantly enhances search capabilities. Tuning Elasticsearch itself is crucial for performance, especially under load.

JVM Heap Size Configuration

The most critical Elasticsearch tuning parameter is the JVM heap size. It should be set to no more than 50% of your system’s physical RAM, and never exceed 30-32GB due to compressed ordinary object pointers (compressed oops).

Edit the Elasticsearch JVM options file. The location varies by installation method:

  • Debian/Ubuntu (package): `/etc/elasticsearch/jvm.options`
  • Tarball: `config/jvm.options` within the Elasticsearch installation directory
# Set Xms and Xmx to the same value for predictable performance.
# Example for a server with 32GB RAM:
-Xms16g
-Xmx16g

Restart Elasticsearch after changes:

sudo systemctl restart elasticsearch

Index Settings and Sharding

For WordPress content, a single index is common. However, consider the number of shards and replicas. Too many shards can strain the cluster, while too few might limit parallelism. For a typical WordPress site, 1-3 primary shards per index is often sufficient. Replicas provide redundancy and can improve read performance but increase storage and indexing overhead.

You can set these during index creation or update them later. Example using `curl` to update settings for an index named `wordpress`:

# Check current settings
curl -X GET "localhost:9200/wordpress/_settings?pretty"

# Update settings (e.g., to 2 shards, 1 replica)
curl -X PUT "localhost:9200/wordpress/_settings?pretty" -H 'Content-Type: application/json' -d'
{
  "index" : {
    "number_of_shards" : "2",
    "number_of_replicas" : "1"
  }
}
'

Query Optimization and Caching

Elasticsearch has its own query cache and request cache. Ensure they are enabled and appropriately sized. For WordPress, focus on efficient search queries. Plugins like ElasticPress often handle much of this, but understanding the underlying Elasticsearch queries is beneficial.

Monitor Elasticsearch performance using tools like Kibana’s Stack Monitoring or dedicated APM solutions. Key metrics include JVM heap usage, CPU utilization, indexing latency, and search latency.

Putting It All Together: Nginx as a Reverse Proxy

Nginx will act as the primary entry point, routing traffic to either PHP-FPM (for dynamic WordPress content) or serving static assets directly. If using Gunicorn, Nginx would also proxy requests to Gunicorn.

Nginx Site Configuration Example

Consider a WordPress site served by PHP-FPM and static assets. Your Nginx virtual host configuration (e.g., `/etc/nginx/sites-available/your-wordpress-site`) might look like this:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    root /var/www/your-wordpress-site;
    index index.php index.html index.htm;

    # SSL configuration (highly recommended)
    # listen 443 ssl http2;
    # ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    # include /etc/letsencrypt/options-ssl-nginx.conf;
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Cache static assets for a long time
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp|woff|woff2)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
        access_log off;
        log_not_found off;
    }

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

    # Serve static files directly if they exist
    location ~ ^/(wp-content/uploads/.*)$ {
        try_files $uri =404;
    }

    # Pass PHP scripts to PHP-FPM
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # With php-fpm (or other unix sockets):
        fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Adjust PHP version
        # With php-cgi (or other tcp sockets):
        # fastcgi_pass 127.0.0.1:9000;

        # Include security headers and optimizations
        fastcgi_read_timeout 300; # Increase timeout for long-running scripts
        fastcgi_buffer_size 128k;
        fastcgi_buffers 8 128k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;

        # WordPress specific rules
        # try_files $uri =404; # This should be handled by WordPress itself
    }

    # WordPress permalink rules
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self';"; # Example CSP, requires careful tuning

    # 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 image/svg+xml;
}

Enable the site and test Nginx configuration:

sudo ln -s /etc/nginx/sites-available/your-wordpress-site /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

This comprehensive approach, combining Nginx, PHP-FPM (or Gunicorn), and Elasticsearch tuning, provides a robust foundation for high-performance WordPress deployments on OVH infrastructure.

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

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

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

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala