• 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 Elasticsearch on Linode for Shopify

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

Nginx as a High-Performance Frontend for Shopify Applications

When deploying a Shopify application backend (e.g., a custom app or a headless CMS) on Linode, Nginx serves as the critical entry point. Its role extends beyond simple request routing; it’s a powerful reverse proxy, load balancer, and static file server. Optimizing Nginx is paramount for handling high traffic volumes and ensuring low latency.

A common setup involves Nginx proxying requests to a backend application server like Gunicorn (for Python/Django/Flask) or PHP-FPM (for PHP/Laravel/Symfony). We’ll focus on tuning Nginx for this specific scenario, assuming a single Linode instance for simplicity, but the principles scale to multi-server deployments.

Nginx Configuration Tuning

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

Worker Processes and Connections

The number of worker processes should ideally match the number of CPU cores available on your Linode instance. This allows Nginx to effectively utilize all available processing power for handling concurrent connections. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024, but this can be increased based on your application’s needs and system resources.

# /etc/nginx/nginx.conf

user www-data;
worker_processes auto; # Or set to the number of CPU cores, e.g., worker_processes 4;

events {
    worker_connections 4096; # Increased from default 1024
    multi_accept on;
}

http {
    # ... other http configurations ...

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off; # Important for security and to avoid revealing Nginx version

    # ... other http configurations ...
}

Explanation:

  • worker_processes auto;: Nginx will automatically determine the optimal number of worker processes based on the number of CPU cores.
  • worker_connections 4096;: Allows each worker to handle a significantly higher number of concurrent connections.
  • multi_accept on;: Enables a worker to accept multiple new connections at once.
  • sendfile on;: Optimizes file transfers by using the sendfile() system call, reducing data copying between kernel and user space.
  • tcp_nopush on;: Instructs Nginx to send header information and the first data packet in a single write operation.
  • tcp_nodelay on;: Disables the Nagle algorithm, which can reduce latency for real-time applications.
  • keepalive_timeout 65;: Sets the timeout for persistent connections.
  • server_tokens off;: Hides the Nginx version from HTTP response headers, a minor security hardening measure.

Proxying to Gunicorn/PHP-FPM

The location block is where we define how Nginx forwards requests to the backend application server. For Gunicorn, this typically involves a Unix socket or a TCP port. For PHP-FPM, it’s usually a Unix socket.

Gunicorn Example (Python)

Assuming Gunicorn is running and listening on a Unix socket at /run/gunicorn.sock.

# /etc/nginx/sites-available/your_shopify_app

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;

    location / {
        proxy_pass http://unix:/run/gunicorn.sock;
        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_read_timeout 300s; # Increased timeout for potentially long requests
        proxy_connect_timeout 75s;
    }

    # Serve static files directly from Nginx for performance
    location /static/ {
        alias /path/to/your/app/static/;
        expires 30d;
        access_log off;
    }

    # Handle favicon and robots.txt
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
}

PHP-FPM Example (PHP)

Assuming PHP-FPM is running and listening on a Unix socket at /var/run/php/php7.4-fpm.sock (adjust version as needed).

# /etc/nginx/sites-available/your_shopify_app

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;
    root /var/www/your_shopify_app/public; # Adjust to your public directory
    index index.php index.html index.htm;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Use the correct socket path for your PHP-FPM version
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Serve static files directly from Nginx
    location ~* \.(css|js|jpg|jpeg|gif|png|svg|ico|webp)$ {
        expires 30d;
        access_log off;
        add_header Cache-Control "public";
    }

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

Explanation:

  • proxy_pass: Directs the request to the backend server.
  • proxy_set_header: Passes essential client information to the backend application.
  • proxy_read_timeout and proxy_connect_timeout: Crucial for preventing timeouts on long-running requests. Adjust these values based on your application’s typical request duration.
  • location /static/ or location ~* \.(css|js|...): Offloads static file serving to Nginx, which is significantly more efficient than serving them through the application server.
  • expires 30d;: Sets aggressive caching headers for static assets.
  • try_files (PHP-FPM): A common directive for frameworks to handle routing.
  • fastcgi_pass (PHP-FPM): Specifies the PHP-FPM socket.

Gunicorn/PHP-FPM Tuning for Performance

The application server itself needs to be tuned to handle the load efficiently. This involves adjusting the number of worker processes and their configuration.

Gunicorn Tuning

Gunicorn’s performance is heavily influenced by its worker class and the number of worker processes. The sync worker class is the default and simplest, but for I/O-bound applications, the gevent or eventlet worker classes can offer better concurrency.

A common recommendation for the number of workers is (2 * number_of_cores) + 1. However, this can vary based on whether your application is CPU-bound or I/O-bound.

# Example command to start Gunicorn with optimal workers
# Assuming a WSGI application file named 'wsgi.py' in the current directory

gunicorn --workers 5 --worker-class gevent --bind unix:/run/gunicorn.sock wsgi:app
# Or for a TCP bind:
# gunicorn --workers 5 --worker-class gevent --bind 0.0.0.0:8000 wsgi:app

Explanation:

  • --workers 5: Sets the number of worker processes. Adjust based on your Linode’s CPU cores and application type.
  • --worker-class gevent: Uses the gevent worker class for asynchronous I/O.
  • --bind unix:/run/gunicorn.sock: Binds Gunicorn to a Unix socket, which is generally faster than TCP for local communication.

PHP-FPM Tuning

PHP-FPM has several process management modes: static, dynamic, and ondemand. For predictable high traffic, static is often preferred for its consistent performance, while dynamic offers a balance between resource usage and responsiveness.

The configuration file for PHP-FPM is typically found at /etc/php/[version]/fpm/pool.d/www.conf.

; /etc/php/7.4/fpm/pool.d/www.conf (example for PHP 7.4)

; Choose the process management mode
; pm = dynamic
pm = static

; For 'static' mode:
; Set the number of child processes to start.
; A common starting point is (2 * number_of_cores) + 1.
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

; For 'dynamic' mode (if chosen):
; pm.max_children = 50
; pm.start_servers = 5
; pm.min_spare_servers = 2
; pm.max_spare_servers = 8
; pm.max_requests = 500 ; Restart child processes after this many requests

; Adjust the listen socket to match Nginx configuration
listen = /var/run/php/php7.4-fpm.sock
; listen.owner = www-data
; listen.group = www-data
; listen.mode = 0660

; Other important settings:
; request_terminate_timeout = 120 ; Timeout for script execution in seconds
; pm.process_idle_timeout = 10s ; For dynamic mode, timeout for idle processes

Explanation:

  • pm = static: Ensures a fixed number of child processes are always available.
  • pm.max_children: The maximum number of child processes that will be created. This should be carefully tuned to avoid exhausting server memory.
  • pm.start_servers, pm.min_spare_servers, pm.max_spare_servers: These parameters control the dynamic scaling of processes when pm = dynamic is used.
  • pm.process_idle_timeout: For dynamic mode, how long an idle process will be kept alive.
  • pm.max_requests: Useful for preventing memory leaks by restarting processes after a certain number of requests.
  • listen: The socket PHP-FPM listens on. Must match Nginx’s fastcgi_pass directive.

Elasticsearch Tuning for Shopify Data

If your Shopify application involves complex search, analytics, or logging that leverages Elasticsearch, tuning it is crucial. Elasticsearch performance is highly dependent on JVM heap size, file system cache, and shard configuration.

JVM Heap Size

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

# /etc/elasticsearch/jvm.options (or similar path depending on installation)

-Xms4g
-Xmx4g

Explanation:

  • -Xms4g: Sets the initial Java heap size to 4GB.
  • -Xmx4g: Sets the maximum Java heap size to 4GB.

Recommendation: Set both -Xms and -Xmx to the same value to prevent the JVM from resizing the heap, which can cause pauses. For a 16GB Linode, 8GB might be appropriate. For a 32GB Linode, 16GB is a good starting point. Always monitor memory usage.

File System Cache

Elasticsearch relies heavily on the operating system’s file system cache. Ensure your Linode has sufficient free RAM for the OS to cache index data. Avoid running other memory-intensive applications on the same server as Elasticsearch.

On Linux, you can monitor file system cache usage using free -h or vmstat. The “buff/cache” column in free -h indicates memory used for caching.

# Example output from 'free -h'
              total        used        free      shared  buff/cache   available
Mem:            15G         3.0G         8.0G        100M         4.0G         11G
Swap:          2.0G           0B         2.0G

In this example, 4GB is used for buff/cache, leaving 11GB available for applications and the JVM heap. For Elasticsearch, you want this “available” memory to be as high as possible after accounting for the JVM heap.

Index and Shard Configuration

The number of shards and replicas significantly impacts performance and scalability. For a single-node setup, replicas are less critical for high availability but add overhead. For search performance, consider the number of primary shards.

Rule of Thumb: Aim for shard sizes between 10GB and 50GB. Too many small shards increase overhead; too few large shards can make rebalancing slow.

When creating an index, specify the number of primary shards. For a single-node setup, 1 primary shard is often sufficient. If you anticipate scaling to multiple nodes later, consider 3 or 5 primary shards.

# Example: Creating an index with 1 primary shard and 0 replicas
PUT /my_shopify_index
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0
    }
  }
}

Monitoring and Iteration

Performance tuning is an iterative process. Continuously monitor your system’s performance using tools like:

  • Nginx: nginx -s reload for applying config changes, /var/log/nginx/access.log and error.log, Nginx Amplify, or Prometheus/Grafana with the Nginx exporter.
  • Gunicorn/PHP-FPM: Application logs, system monitoring tools (htop, top), and specific metrics exposed by Gunicorn or PHP-FPM.
  • Elasticsearch: Elasticsearch’s own monitoring APIs (_cat APIs, _nodes/stats, _cluster/stats), Kibana’s Stack Monitoring, or Prometheus/Grafana with the Elasticsearch exporter.
  • System: Linode’s dashboard, htop, vmstat, iostat.

Adjust configurations based on observed bottlenecks. For instance, if Nginx is showing high CPU usage, investigate worker connections or upstream timeouts. If Elasticsearch queries are slow, check JVM heap, shard sizes, and disk I/O.

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