• 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 Shopify

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

Nginx as a High-Performance Frontend for Shopify Applications

When deploying a Shopify application backend, especially one leveraging custom logic or microservices, Nginx serves as the indispensable frontend. Its role extends beyond simple request routing; it’s a critical component for caching, SSL termination, load balancing, and static file serving. Optimizing Nginx is paramount for achieving low latency and high throughput.

Our OVH infrastructure typically involves a dedicated Nginx instance acting as the gateway. The primary configuration file, often located at /etc/nginx/nginx.conf, and site-specific configurations in /etc/nginx/sites-available/ (symlinked to /etc/nginx/sites-enabled/) are the focal points for tuning.

Core Nginx Tuning Parameters

The http block within nginx.conf is where many global settings reside. For high-traffic applications, increasing the number of worker processes and connections per worker is crucial.

Worker Processes and Connections

The worker_processes directive should ideally be set to the number of CPU cores available to the Nginx instance. 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.

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

events {
    worker_connections 1024; # 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;
    server_tokens off; # Important for security

    # ... other http configurations
}

server_tokens off; is a critical security hardening step, preventing Nginx from revealing its version in HTTP headers, which could be exploited by attackers.

Optimizing for Application Backends (Gunicorn/FPM)

When Nginx proxies requests to application servers like Gunicorn (for Python) or PHP-FPM, efficient proxying configurations are key. This involves setting appropriate timeouts, buffer sizes, and enabling HTTP/2 for reduced latency.

Proxy Buffering and Timeouts

Buffering can sometimes introduce latency if not configured correctly. For long-running requests or streaming, disabling or adjusting buffer sizes might be necessary. However, for typical API interactions, default or slightly tuned buffers are often sufficient. More critical are the timeouts.

# /etc/nginx/sites-available/your_shopify_app
server {
    listen 80;
    listen [::]:80;
    server_name your-app.example.com;

    # Redirect HTTP to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your-app.example.com;

    ssl_certificate /etc/letsencrypt/live/your-app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-app.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Proxy settings for Gunicorn/Python backend
    location / {
        proxy_pass http://127.0.0.1:8000; # Assuming Gunicorn is on port 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;

        proxy_connect_timeout 75s;
        proxy_send_timeout 75s;
        proxy_read_timeout 75s;

        # Optional: Disable buffering for specific scenarios if needed
        # proxy_buffering off;
    }

    # Proxy settings for PHP-FPM backend
    # location ~ \.php$ {
    #     include snippets/fastcgi-php.conf;
    #     fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version
    #     fastcgi_read_timeout 300; # Longer timeout for PHP scripts
    # }

    # Serve static files directly
    location ~ ^/(images|javascript|js|css|flash|media|static)/ {
        root /var/www/your_shopify_app/public; # Adjust path to your static assets
        expires 30d;
        access_log off;
        add_header Cache-Control "public, max-age=2592000";
    }

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

The proxy_read_timeout is particularly important. If your application backend takes longer than this to respond, Nginx will close the connection. For long-running Shopify webhook processors or complex data imports, this value might need to be increased significantly (e.g., 300s or more). Conversely, for highly responsive APIs, shorter timeouts can help fail fast and prevent resource exhaustion.

Caching Strategies

Leveraging Nginx’s built-in caching can dramatically reduce load on your application servers and improve response times for frequently accessed, non-dynamic content. This is especially relevant for static assets and even some API responses that don’t change often.

# In the http block of nginx.conf
proxy_cache_path /var/cache/nginx/your_app levels=1:2 keys_zone=your_app_cache:10m max_size=10g inactive=60m use_temp_path=off;

# In your server block configuration
location / {
    proxy_pass http://127.0.0.1:8000;
    # ... other proxy settings

    proxy_cache your_app_cache;
    proxy_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
    proxy_cache_valid 404 1m;      # Cache 404s for 1 minute
    proxy_cache_key "$scheme$request_method$host$request_uri";
    add_header X-Cache-Status $upstream_cache_status; # Useful for debugging
}

The proxy_cache_path directive defines the location and parameters for the cache. keys_zone allocates shared memory for cache keys. max_size limits the total cache size. The proxy_cache_valid directive specifies how long different HTTP status codes should be cached. The X-Cache-Status header is invaluable for verifying if a response was served from the cache.

Gunicorn/PHP-FPM: The Application Backend Engine

The performance of your Shopify application is directly tied to the efficiency of its backend process manager. For Python applications, Gunicorn is a popular choice. For PHP, PHP-FPM is the standard. Tuning these components is critical for handling concurrent requests and managing resources effectively.

Gunicorn Tuning for Python Applications

Gunicorn’s concurrency model is based on worker processes. The choice of worker class and the number of workers significantly impact performance and stability. For I/O-bound applications (common in web services), the gevent or event worker classes are often preferred over the default sync worker.

Worker Class and Count

A common recommendation for the number of workers is (2 * number_of_cores) + 1. However, for I/O-bound tasks, you might need more workers to keep the CPU busy while waiting for I/O operations. For gevent or event workers, the number of workers can be higher as they are asynchronous.

# Example Gunicorn command line or systemd service file
# Assuming 4 CPU cores on the server

# Using event worker (default for many setups, good balance)
gunicorn --workers 5 --worker-class gevent --bind 127.0.0.1:8000 your_app.wsgi:application

# Or using gevent worker (requires gevent installed: pip install gevent)
# gunicorn --workers 5 --worker-class gevent --bind 127.0.0.1:8000 your_app.wsgi:application

# For CPU-bound tasks, sync worker might be simpler but less concurrent
# gunicorn --workers 2 --bind 127.0.0.1:8000 your_app.wsgi:application

The --worker-connections parameter is relevant for gevent and event workers, determining how many concurrent requests each worker can handle. A value of 1000 is often a good starting point.

# Example with worker_connections
gunicorn --workers 5 --worker-class gevent --worker-connections 1000 --bind 127.0.0.1:8000 your_app.wsgi:application

Timeouts and Keep-Alive

Gunicorn’s --timeout setting dictates how long a worker can take to process a request before being restarted. This should generally be higher than Nginx’s proxy_read_timeout to avoid premature worker restarts. --keep-alive controls the number of requests a worker can handle before being gracefully restarted, helping to prevent memory leaks.

gunicorn --workers 5 --worker-class gevent --worker-connections 1000 --timeout 120 --keep-alive 1000 --bind 127.0.0.1:8000 your_app.wsgi:application

PHP-FPM Tuning for PHP Applications

PHP-FPM (FastCGI Process Manager) is highly configurable. The primary configuration file is typically /etc/php/X.Y/fpm/php-fpm.conf and pool configurations are in /etc/php/X.Y/fpm/pool.d/www.conf.

Process Management (pm)

PHP-FPM offers several process management strategies: static, dynamic, and ondemand. For predictable high-traffic loads, static is often best as it pre-forks a fixed number of processes. dynamic is a good compromise, starting with a few processes and spawning more as needed, up to a defined maximum.

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

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process Management (choose one)
; pm = static
; pm.max_children = 50 ; Number of processes to keep running

pm = dynamic
pm.max_children = 100    ; Maximum number of children that can be started.
pm.min_spare_servers = 5 ; Minimum number of children to keep in the pool.
pm.max_spare_servers = 20; Maximum number of children to keep in the pool.
pm.process_idle_timeout = 10s ; The timeout for a child process to become idle.

; pm = ondemand
; pm.max_children = 50
; pm.process_idle_timeout = 10s

The values for pm.max_children, pm.min_spare_servers, and pm.max_spare_servers should be tuned based on server RAM and CPU. A common starting point for max_children is (total_ram_in_MB / average_process_size_in_MB). Monitor memory usage closely.

Request and Process Timeouts

request_terminate_timeout is crucial for preventing runaway scripts. It’s the maximum time a script can execute before being terminated. This should be set lower than Nginx’s proxy_read_timeout to ensure Nginx can gracefully handle a terminated script. process_idle_timeout (for dynamic and ondemand) helps manage idle processes.

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

; ... other settings ...

; The timeout for serving a single request.
; It should be lower than 'max_execution_time' in php.ini.
; Default is 0 (means unlimited).
request_terminate_timeout = 60s

; Set to '0' if you want to disable idle timeout.
; Default is 10s.
process_idle_timeout = 10s

Ensure that PHP’s max_execution_time in php.ini is also configured appropriately, and ideally, set to be equal to or greater than request_terminate_timeout.

Elasticsearch Performance Tuning on OVH

For Shopify applications that rely on robust search capabilities, Elasticsearch is often the chosen solution. Optimizing Elasticsearch on OVH infrastructure involves JVM heap tuning, shard management, and query optimization.

JVM Heap Size Configuration

The Java Virtual Machine (JVM) heap size is the most critical setting for Elasticsearch performance. It dictates how much memory Elasticsearch can use for its operations. Setting it too low leads to frequent garbage collection (GC) pauses, while setting it too high can starve the OS of memory.

The general rule of thumb is to set the heap size to no more than 50% of the system’s RAM, and never exceed 30-32GB. This is because of compressed ordinary object pointers (compressed oops), which provide significant memory savings when the heap is below this threshold.

# Elasticsearch JVM options file: /etc/elasticsearch/jvm.options
# Example for a server with 32GB RAM

-Xms4g
-Xmx4g
-XX:MaxDirectMemorySize=4g

The -Xms (initial heap size) and -Xmx (maximum heap size) should be set to the same value to prevent the JVM from resizing the heap, which can cause pauses. MaxDirectMemorySize is also important for off-heap memory usage.

Shard Management and Allocation

The number and size of shards directly impact search performance and cluster stability. Too many small shards can increase overhead, while too few large shards can lead to slow recovery and uneven disk I/O distribution.

For Shopify data, consider the lifecycle of your data. Product data might be relatively static, while order data is more dynamic. Indexing strategies should reflect this. Avoid over-sharding. A common recommendation is to keep shard sizes between 10GB and 50GB.

# Example: Adjusting number of primary shards for a new index
PUT /my_shopify_products
{
  "settings": {
    "index": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    }
  }
}

The number of primary shards should be determined at index creation and should not be changed later. Replicas can be adjusted dynamically. Ensure you have enough nodes to accommodate the shards and their replicas without exceeding the recommended shard count per GB of heap.

Query Optimization and Indexing

Inefficient queries are a major performance bottleneck. Use Elasticsearch’s Profile API to analyze slow queries. Avoid leading wildcards (`*term`) and excessive use of `script` queries. Optimize mappings to use appropriate data types (e.g., keyword for exact matches, text for full-text search).

# Example of a slow query that could be optimized
GET /_search
{
  "profile": true,
  "query": {
    "bool": {
      "must": [
        { "wildcard": { "product_name": "*widget" } }
      ]
    }
  }
}

For Shopify product catalogs, consider using the completion suggester for type-ahead search or carefully crafted match queries with appropriate analyzers instead of broad wildcard searches. Regularly review and optimize index mappings and settings.

Monitoring and Iterative Tuning

Performance tuning is not a one-time task. Continuous monitoring and iterative adjustments are essential. Utilize tools like Prometheus with Node Exporter and Elasticsearch Exporter, Grafana for visualization, and Nginx’s built-in status module or stub_status module.

Key metrics to watch include:

  • Nginx: Active connections, requests per second, error rates (4xx, 5xx), upstream response times.
  • Gunicorn/PHP-FPM: Worker utilization, request queue length, response times, error rates, memory usage per worker.
  • Elasticsearch: JVM heap usage, GC activity, indexing latency, search latency, disk I/O, CPU utilization, thread pool queues.
  • System: CPU load, memory usage, disk I/O, network traffic.

Regularly analyze these metrics to identify bottlenecks and areas for improvement. Implement changes incrementally and measure their impact. This systematic approach ensures that your Shopify application backend on OVH remains performant and scalable.

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

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (48)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • 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