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

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

Nginx as a High-Performance Frontend for Shopify Applications

When deploying a Shopify application backend, particularly one built with Python (e.g., using Django or Flask) or PHP, Nginx serves as the de facto standard for a robust and performant frontend. Its event-driven architecture excels at handling a high volume of concurrent connections, making it ideal for serving static assets, proxying requests to application servers, and implementing crucial security and caching layers. This section focuses on tuning Nginx specifically for this role on a Linode VPS.

Core Nginx Configuration Tuning

The primary configuration file, typically located at /etc/nginx/nginx.conf, contains global settings that significantly impact performance. We’ll focus on the events and http blocks.

Optimizing the events Block

The events block controls how Nginx handles connections. The key directives here are worker_connections and multi_accept.

worker_connections defines the maximum number of simultaneous connections that each worker process can handle. This should be set in conjunction with the system’s file descriptor limit. A common starting point is 1024, but for high-traffic sites, this can be increased significantly. The theoretical maximum is limited by the OS’s file descriptor limit (ulimit -n).

multi_accept on; allows a worker process to accept as many new connections as possible in a single go, rather than accepting them one by one. This can improve performance under heavy load.

Here’s a sample tuning for the events block:

events {
    worker_connections 4096; # Adjust based on system resources and expected load
    multi_accept on;
    use epoll; # For Linux, epoll is generally the most performant event notification mechanism
}

Tuning the http Block

The http block contains settings that apply to all virtual hosts. Key directives for performance include keepalive_timeout, sendfile, tcp_nopush, tcp_nodelay, and gzip.

keepalive_timeout: Controls how long an idle HTTP connection will remain open. A shorter timeout reduces server resource usage but might increase overhead for clients making frequent requests. A value between 65 and 75 seconds is a good balance.

sendfile on;: Enables the sendfile() system call, which allows Nginx to transfer files directly from the kernel’s page cache to the socket, bypassing user space. This significantly reduces CPU usage and memory overhead for serving static files.

tcp_nopush on;: Instructs Nginx to try and send HTTP response headers in one packet, along with any preceding data, if possible. This can reduce the number of packets sent over the network.

tcp_nodelay on;: Disables the Nagle algorithm. This is generally beneficial for latency-sensitive applications, ensuring that packets are sent as soon as they are available, rather than waiting to be buffered.

gzip on;: Enables Gzip compression for responses. This is crucial for reducing bandwidth usage and improving load times for text-based assets like HTML, CSS, and JavaScript. Further tuning of gzip_types, gzip_min_length, and gzip_comp_level is recommended.

Here’s an example of an optimized http block:

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  75;
    types_hash_max_size 2048; # Increase if you have a very large number of MIME types

    # Gzip Compression Settings
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6; # Compression level (1-9)
    gzip_min_length 256; # Minimum response length to compress
    gzip_buffers 16 8k; # Number and size of buffers
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # Optional: Enable brotli compression if compiled with it
    # brotli on;
    # brotli_comp_level 6;
    # brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # Include server blocks for your specific applications
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Proxying to Gunicorn (Python) or PHP-FPM

Nginx’s primary role for dynamic content is to act as a reverse proxy. The configuration within your site-specific Nginx configuration file (e.g., /etc/nginx/sites-available/your_shopify_app) is critical.

Nginx with Gunicorn (WSGI)

When using Gunicorn to serve a Python web application, Nginx typically communicates with Gunicorn via a Unix socket or a TCP port. Unix sockets are generally preferred for performance and security when Nginx and Gunicorn are on the same machine.

Key directives include proxy_pass, proxy_set_header, and proxy_read_timeout.

proxy_pass: Specifies the address of the upstream server (Gunicorn). If using a Unix socket, it would look like unix:/path/to/your/app.sock. If using TCP, it would be http://127.0.0.1:8000.

proxy_set_header: Essential for passing client information to the backend application. Directives like Host, X-Real-IP, and X-Forwarded-For are vital for the application to correctly identify the original client.

proxy_read_timeout: Sets the timeout for reading a response from the upstream server. For long-running requests, this needs to be sufficiently high to prevent premature timeouts.

Example Nginx configuration for Gunicorn:

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

    # Serve static files directly
    location /static/ {
        alias /path/to/your/app/static/;
        expires 30d;
        add_header Cache-Control "public";
    }

    # Proxy dynamic requests to Gunicorn
    location / {
        proxy_pass http://unix:/run/gunicorn.sock; # Or http://127.0.0.1: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_read_timeout 300s; # Adjust for long-running tasks
        proxy_send_timeout 75s;
    }

    # Optional: Handle specific API endpoints with longer timeouts
    location /api/ {
        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 600s; # Even longer for specific API calls
    }

    # Access and error logs
    access_log /var/log/nginx/your_app_access.log;
    error_log /var/log/nginx/your_app_error.log;
}

Nginx with PHP-FPM

For PHP applications, Nginx acts as a reverse proxy to PHP-FPM, the FastCGI Process Manager. Communication is typically via a Unix socket or a TCP port.

The fastcgi_pass directive is used instead of proxy_pass. Other important directives include fastcgi_param, fastcgi_read_timeout, and fastcgi_buffers.

fastcgi_pass: Specifies the address of the PHP-FPM pool. E.g., unix:/var/run/php/php7.4-fpm.sock or 127.0.0.1:9000.

fastcgi_param: Sets FastCGI parameters. Crucially, SCRIPT_FILENAME must be set correctly for PHP to find the requested script.

fastcgi_read_timeout: Similar to proxy_read_timeout, this sets the timeout for reading a response from PHP-FPM.

fastcgi_buffers and fastcgi_buffer_size: Control the buffering of FastCGI responses. Increasing these can help with large output from PHP scripts.

Example Nginx configuration for PHP-FPM:

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

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf; # Common FastCGI settings
        # Or define parameters explicitly:
        # fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        # fastcgi_index index.php;
        # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # include fastcgi_params;

        # Use the correct PHP-FPM socket or address
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Example for PHP 8.1
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $fastcgi_path_translated;

        fastcgi_read_timeout 300s; # Adjust for long-running PHP scripts
        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
    }

    # Deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny all;
    }

    # Access and error logs
    access_log /var/log/nginx/your_app_access.log;
    error_log /var/log/nginx/your_app_error.log;
}

System-Level Tuning for Nginx

Beyond Nginx’s configuration, the underlying operating system needs to be tuned to support high concurrency.

File Descriptor Limits

Each connection and open file consumes a file descriptor. Ensure your system’s limits are set appropriately. This is typically managed via /etc/security/limits.conf and systemd service files.

Edit /etc/security/limits.conf:

# Increase open file limits for the nginx user
nginx soft nofile 65536
nginx hard nofile 65536

For systemd services (like Nginx or Gunicorn), you might need to configure limits within the service unit file:

[Service]
LimitNOFILE=65536
LimitNOFILESoft=65536

After modifying limits.conf, you’ll need to log out and log back in for the changes to take effect for your user. For systemd services, reload the daemon and restart the service:

sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl restart gunicorn # or php-fpm

Network Stack Tuning (sysctl)

Optimizing kernel network parameters can improve TCP performance and connection handling.

Edit /etc/sysctl.conf and add or modify the following parameters:

# Increase the maximum number of open files
fs.file-max = 2097152

# Increase the maximum number of file descriptors that can be opened by a process
# This is a system-wide limit, often set higher than individual user limits
kernel.pid_max = 65536

# Network tuning
net.core.somaxconn = 4096 # Maximum number of connections queued
net.core.netdev_max_backlog = 2000 # Max number of packets queued on the input side
net.ipv4.tcp_max_syn_backlog = 2048 # Max number of outstanding SYN
net.ipv4.tcp_synack_retries = 2 # Number of SYN-ACK retries
net.ipv4.tcp_tw_reuse = 1 # Allow reuse of TIME-WAIT sockets
net.ipv4.tcp_fin_timeout = 30 # Timeout for closing sockets
net.ipv4.ip_local_port_range = 1024 65535 # Range of ephemeral ports
net.ipv4.tcp_timestamps = 0 # Disable TCP timestamps (can save a little bandwidth, but might affect some diagnostics)
net.ipv4.tcp_syncookies = 1 # Enable TCP syncookies to protect against SYN floods

Apply the changes:

sudo sysctl -p

Gunicorn Performance Tuning for Python Applications

Gunicorn (Green Unicorn) is a Python WSGI HTTP Server. Its performance is heavily influenced by its worker configuration and how it interacts with the OS.

Worker Types and Counts

Gunicorn supports several worker types:

  • Sync Workers (sync): The default. Each worker is a single process that handles one request at a time. This is simple but can be a bottleneck under high concurrency.
  • Asynchronous Workers (eventlet, gevent): These workers use non-blocking I/O and can handle multiple requests concurrently within a single process using green threads. This is generally the preferred choice for I/O-bound applications.
  • Threaded Workers (gthread): Uses threads within a single process. Less common for web applications due to Python’s Global Interpreter Lock (GIL), but can be useful for specific CPU-bound tasks if carefully managed.

The number of workers is crucial. A common recommendation is (2 * number_of_cores) + 1. However, for I/O-bound applications using async workers, you might need more workers to keep all CPU cores busy while waiting for I/O. For sync workers, this formula is more appropriate.

Example Gunicorn command-line configuration:

gunicorn --workers 4 \
         --worker-class gevent \
         --bind unix:/run/gunicorn.sock \
         --timeout 300 \
         --graceful-timeout 300 \
         --log-level info \
         your_app.wsgi:application

Explanation:

  • --workers 4: Sets the number of worker processes. Adjust based on your Linode instance’s CPU cores.
  • --worker-class gevent: Uses gevent for asynchronous handling.
  • --bind unix:/run/gunicorn.sock: Binds to a Unix socket for Nginx to connect to.
  • --timeout 300: Sets the worker timeout to 300 seconds (5 minutes).
  • --graceful-timeout 300: Time to wait for existing requests to finish during a graceful restart.
  • your_app.wsgi:application: The entry point for your WSGI application.

Gunicorn Configuration File

For more complex configurations, using a Gunicorn configuration file (e.g., gunicorn_config.py) is recommended.

# gunicorn_config.py

import multiprocessing

# Number of worker processes
workers = multiprocessing.cpu_count() * 2 + 1

# Worker class (gevent, eventlet, sync, gthread)
worker_class = 'gevent' # Or 'sync' if not using async libraries

# Bind to a Unix socket or TCP port
# bind = "unix:/run/gunicorn.sock"
bind = "127.0.0.1:8000" # If binding to TCP, Nginx will proxy to this

# Worker timeout (seconds)
timeout = 300

# Graceful timeout (seconds)
graceful_timeout = 300

# Logging
log_level = 'info'
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'

# Other settings
# max_requests = 1000 # Restart workers after this many requests
# preload_app = True # Preload the application to speed up worker startup

To run Gunicorn with a config file:

gunicorn -c /path/to/your/gunicorn_config.py your_app.wsgi:application

PHP-FPM Tuning for PHP Applications

PHP-FPM is the FastCGI Process Manager for PHP. Its performance is critical for PHP applications. The configuration is managed in php-fpm.conf and pool configuration files (e.g., www.conf).

Process Manager Settings

The pm (Process Manager) setting controls how PHP-FPM manages worker processes. The common options are:

  • static: A fixed number of child processes are always kept alive. Good for predictable loads, but can waste resources if idle.
  • dynamic: The number of child processes varies based on load. It starts with a minimum number and spawns more up to a maximum.
  • ondemand: Processes are spawned only when a request arrives and are killed after a period of inactivity. This saves resources but can introduce latency on the first request.

For most production environments, dynamic offers a good balance. For very high-traffic sites with predictable loads, static might offer slightly better performance by eliminating process spawning overhead.

Key directives within the pool configuration (e.g., /etc/php/8.1/fpm/pool.d/www.conf):

  • pm.max_children: The maximum number of child processes to be created when pm is set to dynamic or static.
  • pm.start_servers: The number of child processes to start when the pool starts (for dynamic).
  • pm.min_spare_servers: The minimum number of idle (spare) server processes to maintain (for dynamic).
  • pm.max_spare_servers: The maximum number of idle (spare) server processes to maintain (for dynamic).
  • pm.max_requests: The number of requests each child process should execute before respawning. Setting this helps prevent memory leaks.

Example www.conf tuning:

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock # Or 127.0.0.1:9000

; Choose your process manager
pm = dynamic

; Settings for dynamic PM
pm.max_children = 100       ; Adjust based on RAM and expected concurrency
pm.start_servers = 10       ; Initial number of workers
pm.min_spare_servers = 5    ; Minimum idle workers
pm.max_spare_servers = 20   ; Maximum idle workers
pm.max_requests = 500       ; Respawn worker after this many requests

; Settings for static PM (if chosen)
; pm = static
; pm.max_children = 100

; Settings for ondemand PM (if chosen)
; pm = ondemand
; pm.process_idle_timeout = 10s
; pm.max_children = 100

; Other important settings
request_terminate_timeout = 300 ; Timeout for script execution (seconds)
; php_admin_value[memory_limit] = 256M ; Example: Set memory limit per script
; php_admin_value[upload_max_filesize] = 64M
; php_admin_value[post_max_size] = 64M

After modifying PHP-FPM configuration, reload the service:

sudo systemctl reload php8.1-fpm

MongoDB Performance Tuning on Linode

MongoDB’s performance is heavily dependent on hardware, configuration, and query patterns. On Linode, optimizing disk I/O, memory usage, and network is key.

Storage Engine Choice

MongoDB primarily uses the WiredTiger storage engine. It’s generally the best choice for most workloads due to its document-level concurrency, compression, and caching.

Ensure your Linode instance has sufficient RAM. MongoDB heavily relies on RAM for its cache. A common recommendation is to have enough RAM to hold your working set (frequently accessed data and indexes).

MongoDB Configuration File (mongod.conf)

The main configuration file is typically located at /etc/mongod.conf.

Key Configuration Parameters

storage.wiredTiger.engineConfig.cacheSizeGB: This is arguably the most critical parameter. It defines the maximum amount of RAM WiredTiger can use for its internal cache. A common starting point is to allocate 50% of your system’s RAM to this cache, ensuring you leave enough for the OS and other processes.

operationProfiling.mode and operationProfiling.slowOpThresholdMs: Enable slow query logging to identify performance bottlenecks. Set mode to all or off, and slowOpThresholdMs to a value like 100ms or 200ms.

net.bindIp: If MongoDB is only accessed from the same Linode instance (via Nginx/Gunicorn/PHP-FPM), bind it to 127.0.0.1 for security. If it needs to be accessed remotely, ensure proper firewall rules are in place.

systemLog.path and systemLog.logAppend: Configure logging paths and ensure logs are appended.

Example mongod.conf snippet (adjusting for a 16GB RAM Linode instance):

systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true
  logRotate: reopen

storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 7.5 # Allocate ~50% of 16GB RAM, leaving room for OS/other processes
    collectionConfig:
      blockSize: 4KB # Default, adjust if your data patterns benefit from larger/smaller blocks
    indexConfig:
      prefixCompression: true # Enable prefix compression for indexes

net:
  port: 27017
  bindIp: 127.0.0.1 # Or 0.0.0.0 if remote access is required (with firewall)

operationProfiling:
  mode: slowOp # Log slow operations
  slowOpThresholdMs: 100 # Log operations slower than 100ms

# Sharding settings (if applicable)
# sharding:
#   clusterRole: configsvr
#   # or shardsvr

# Replication settings (if applicable)
# replication:
#   replSetName: rs0

After modifying mongod.conf, restart MongoDB:

sudo systemctl restart mongod

Indexing Strategy

Proper indexing is paramount for MongoDB performance. Analyze your application’s query patterns using the slow query logs and explain() output.

Use the MongoDB shell to analyze queries:

db.collection.find({ field1: "value1", field2: "value2" }).explain("executionStats")

This will show you if indexes are being used, the number of documents scanned, and the query execution time. Create compound indexes where appropriate for queries involving multiple fields.

Monitoring and Diagnostics

Regular monitoring is essential. Use tools like:

  • Linode Cloud Manager: For CPU, RAM, Disk I/O, and Network usage.
  • Nginx Status Module: stub_status to monitor active connections, requests, etc.
  • PHP-FPM Status Page: To monitor active processes, idle processes, and requests.
  • MongoDB Tools: mongostat, mongotop, and the MongoDB shell’s db.serverStatus() and db.stats().
  • Application Performance Monitoring (APM) tools: e.g., New Relic, Datadog, Sentry for deeper application-level insights.

For Nginx, enable the stub_status module in your nginx.conf:

http {
    # ... other http settings ...

    server {
        listen 80;
        server_name status.your_domain.com; # Or a specific internal IP/port

        location /nginx_status {
            stub_status on;
            allow 127.0.0.1; # Restrict access
            deny all;
        }
    }
}

For PHP-FPM, enable the status page in your pool configuration (e.g., www.conf):

; Add this to your pool configuration
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong

Then, in your Nginx site configuration, add a location block to serve it:

location ~ ^/(fpm-status|fpm-ping)$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Match your PHP-FPM socket
    include fastcgi_params;
    allow 127.0.0.1;
    deny all;
}

Use mongostat and mongotop from the command line for real-time MongoDB insights:

# Real-time connection and operation stats
mongostat --host 127.0.0.1:27017 --discover --noheaders --interval 5

# Real-time document lock analysis
mongotop --host 127.0.0.1:27017 --discover --noheaders --interval 5

By systematically tuning Nginx, your application server (Gunicorn/PHP-FPM), and your database (MongoDB), you can build a highly performant and scalable infrastructure on Linode for your Shopify backend.

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

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals

Categories

  • apache (1)
  • Business & Monetization (386)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (563)
  • DevOps (7)
  • DevOps & Cloud Scaling (949)
  • Django (1)
  • Migration & Architecture (167)
  • MySQL (1)
  • Performance & Optimization (754)
  • PHP (5)
  • Plugins & Themes (223)
  • Security & Compliance (539)
  • SEO & Growth (483)
  • Server (23)
  • Ubuntu (9)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (302)

Recent Posts

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals
  • Top 100 SEO and Schema Markup Plugins for Headless Decoupled Sites for Independent Web Developers and Indie Hackers

Top Categories

  • DevOps & Cloud Scaling (949)
  • Performance & Optimization (754)
  • Debugging & Troubleshooting (563)
  • Security & Compliance (539)
  • SEO & Growth (483)
  • Business & Monetization (386)

Our Products

  • School Management & Student Administration System
  • Integrated Hospital & Clinic Management System
  • Real Estate Directory & Agent Portal
  • Restaurant POS & Table Booking System
  • Retail Inventory POS & Billing System
  • Pharmacy Inventory & Clinic Billing System

Our Services

  • Vibe Engineering & AI Code Auditing Services
  • Prompt Engineering & "Vibe Coding" Workflow Consulting
  • AI-Augmented "Vibe Coding" & Rapid MVP Development
  • Figma to Shopify Liquid Theme Customization
  • Figma to WooCommerce Frontend Development
  • Figma to Magento 2 Theme Development

Copyright © 2026 · Vinay Vengala