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

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

Nginx as a High-Performance Frontend for PHP/Python Applications

When deploying PHP (via FPM) or Python (via Gunicorn) applications on DigitalOcean for a Shopify integration, Nginx serves as the de facto standard for a high-performance frontend. Its event-driven architecture excels at handling concurrent connections, serving static assets, and acting as a robust reverse proxy. The key to unlocking its full potential lies in meticulous configuration, particularly around worker processes, connection limits, and caching.

Nginx Worker Processes and Connections

The `worker_processes` directive dictates how many worker processes Nginx will spawn. Setting this to `auto` is generally recommended, allowing Nginx to dynamically adjust based on the number of CPU cores available. This ensures optimal CPU utilization without over-subscription. Complementing this is `worker_connections`, which defines the maximum number of simultaneous connections a single worker process can handle. The total maximum connections will be `worker_processes * worker_connections`.

A common pitfall is setting `worker_connections` too low, leading to connection exhaustion under load. Conversely, setting it excessively high can lead to resource contention. A good starting point for a DigitalOcean droplet with 2-4 vCPUs is 1024 or 2048. For larger instances, this can be scaled up. It’s crucial to also tune the operating system’s file descriptor limits to accommodate these connections.

Tuning `ulimit` for Nginx

Before Nginx can effectively utilize high connection counts, the underlying operating system must permit it. This is managed via `ulimit` settings, specifically the maximum number of open file descriptors. We need to increase this limit for the user running Nginx (typically `www-data` or `nginx`).

Setting System-Wide Limits

Edit the `/etc/security/limits.conf` file to set persistent limits. Add the following lines, replacing `nofile` with the desired limit (e.g., 65536):

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

Then, edit `/etc/sysctl.conf` to adjust kernel-level network parameters, particularly the maximum number of open files the kernel can allocate.

fs.file-max = 2097152
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_recycle = 0
net.ipv4.ip_local_port_range = 1024 65535
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 2048

Apply these changes immediately with:

sudo sysctl -p

After modifying `limits.conf`, you’ll need to restart the Nginx service or log out and back in for the changes to take effect for the Nginx user.

Nginx Configuration for PHP-FPM

When using PHP-FPM, Nginx acts as a reverse proxy, forwarding requests to the FPM process. The `fastcgi_pass` directive is critical here. It can point to a Unix socket (preferred for performance on a single server) or a TCP port.

Optimizing `fastcgi_params`

Ensure your `fastcgi_params` (or a custom equivalent) includes essential variables. The following is a robust set:

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;
fastcgi_param  HTTPS              $https if_not_empty;
fastcgi_param  REDIRECT_STATUS    200; # For clean URLs
fastcgi_param  HTTP_HOST          $http_host;
fastcgi_param  HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_param  HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
fastcgi_param  HTTP_CONNECTION    $connection_upgrade;

Example Nginx Site Configuration (PHP-FPM)

This configuration prioritizes caching static assets and efficiently proxies dynamic requests to PHP-FPM. Adjust `client_max_body_size` based on expected upload sizes.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096; # Adjusted based on 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;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # Buffering for dynamic content
    proxy_buffering on;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    # SSL Configuration (if applicable)
    # ssl_protocols TLSv1.2 TLSv1.3;
    # ssl_prefer_server_ciphers on;
    # ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # Static File Caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        # Proxy to PHP-FPM
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
        # Use Unix socket for performance if FPM is on the same server
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version as needed
        # Or use TCP if FPM is on a different host/port
        # fastcgi_pass 127.0.0.1:9000;
    }

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

    # PHP-FPM error logging
    location ~ \.php$ {
        include snippets/fastcgi-php.conf; # Or include your custom fastcgi_params
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version as needed
    }
}

Nginx Configuration for Gunicorn (Python)

For Python applications, Gunicorn is a popular WSGI HTTP Server. Nginx will proxy requests to Gunicorn, typically via a Unix socket or a local TCP port. The configuration is similar to PHP-FPM but uses `proxy_pass` instead of `fastcgi_pass`.

Example Nginx Site Configuration (Gunicorn)

This configuration assumes Gunicorn is listening on a Unix socket. Adjust `proxy_read_timeout` and `proxy_connect_timeout` based on your application’s typical response times.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096;
    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;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log warn;

    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # Buffering for dynamic content
    proxy_buffering on;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    # Gunicorn Proxy Settings
    location / {
        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 to Gunicorn Unix Socket
        proxy_pass http://unix:/path/to/your/gunicorn.sock; # Adjust path

        # Or proxy to Gunicorn TCP socket
        # proxy_pass http://127.0.0.1:8000; # Adjust port

        proxy_read_timeout 300s; # Increase for long-running requests
        proxy_connect_timeout 75s;
        proxy_send_timeout 300s;
    }

    # Static File Caching (if Gunicorn serves static files, otherwise serve via Nginx directly)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
        # If static files are served by Gunicorn, remove this block and let proxy_pass handle it.
        # If Nginx serves static files, ensure 'root' directive is set correctly.
    }
}

Gunicorn Configuration Best Practices

Gunicorn’s performance is heavily influenced by its worker count and type. For most applications, the `sync` worker class is stable, but `gevent` or `event` can offer better concurrency if your application is I/O bound.

Worker Count and Type

A common recommendation is `(2 * number_of_cores) + 1`. However, this can be adjusted based on your application’s memory footprint and I/O patterns. For I/O-bound tasks, increasing workers can be beneficial. For CPU-bound tasks, fewer workers might be optimal to avoid context switching overhead.

Example Gunicorn Command Line

This command starts Gunicorn with 4 worker processes (sync type), listening on a Unix socket. Adjust `workers` and `worker_class` as needed.

gunicorn --workers 4 --worker-class sync --bind unix:/path/to/your/gunicorn.sock your_project.wsgi:application

For a TCP socket:

gunicorn --workers 4 --worker-class sync --bind 127.0.0.1:8000 your_project.wsgi:application

Elasticsearch Performance Tuning on DigitalOcean

Elasticsearch, often used for product search and logging within a Shopify integration, can become a performance bottleneck if not properly configured. DigitalOcean droplets, especially those with limited RAM, require careful JVM heap sizing and OS-level tuning.

JVM Heap Size (`jvm.options`)

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

Edit the `jvm.options` file (typically located at `/etc/elasticsearch/jvm.options` or within the Elasticsearch installation directory). Find the lines starting with `-Xms` (initial heap size) and `-Xmx` (maximum heap size) and set them appropriately. For a 4GB RAM droplet, a good starting point is 2GB.

-Xms2g
-Xmx2g

After changing `jvm.options`, restart Elasticsearch:

sudo systemctl restart elasticsearch

OS-Level Tuning for Elasticsearch

Elasticsearch benefits from increased virtual memory limits and disabled swapping.

Disable Swap

Swapping can severely degrade Elasticsearch performance. Disable it and remove swap entries from `/etc/fstab`.

sudo swapoff -a
# Then edit /etc/fstab and comment out or remove swap lines

Increase `vm.max_map_count`

Elasticsearch requires a high number of memory map areas. Edit `/etc/sysctl.conf` or create a file in `/etc/sysctl.d/`:

vm.max_map_count=262144

Apply the change:

sudo sysctl -p

Elasticsearch Indexing and Sharding Strategy

For Shopify integrations, product data is a prime candidate for Elasticsearch. A common mistake is creating overly large indices or an excessive number of shards. For product catalogs, consider:

  • Shards: Start with a small number of primary shards (e.g., 1-3) per index. You can add more later if needed, but reducing them is difficult. The number of shards should ideally be related to the number of data nodes, not the total document count.
  • Replicas: Use replicas for high availability and read performance. A common setup is 1 replica for production.
  • Index Lifecycle Management (ILM): For time-series data (like logs), use ILM to automatically manage indices (rollover, shrink, delete).
  • Mapping: Define explicit mappings for your product data to ensure correct data types and avoid dynamic mapping overhead.

Monitoring and Diagnostics

Regular monitoring is crucial. Use tools like:

  • Nginx: `stub_status` module for connection metrics, `access.log` analysis (e.g., with `goaccess`), and `error.log`.
  • Gunicorn/PHP-FPM: Application logs, process monitoring (e.g., `htop`, `ps`), and potentially APM tools.
  • Elasticsearch: Elasticsearch’s own monitoring APIs (`_cat` APIs, `_nodes/stats`, `_indices/stats`), and external tools like Metricbeat or Prometheus/Grafana.

For Nginx, enable `stub_status` in your `http` block:

http {
    # ... other http settings ...
    server {
        listen 80;
        server_name your_domain.com;
        location /nginx_status {
            stub_status;
            allow 127.0.0.1; # Restrict access
            deny all;
        }
        # ... rest of your server config ...
    }
}

This provides a quick overview of active connections, accepted connections, etc., accessible at `/nginx_status`.

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

  • Orchestrating Serverless PHP 9 with AWS Lambda and API Gateway: A Deep Dive into Performance and Cost Optimization
  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9’s JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices
  • Leveraging PHP 8.3 JIT and Vectorization for High-Throughput Microservices in a Laravel Ecosystem
  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (19)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (24)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (26)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Orchestrating Serverless PHP 9 with AWS Lambda and API Gateway: A Deep Dive into Performance and Cost Optimization
  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9's JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

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