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

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

Nginx as a High-Performance Frontend Proxy

For a PHP application, Nginx serves as an excellent frontend proxy, efficiently handling static assets, SSL termination, and request routing to your application server (Gunicorn for Python/Flask/Django, or PHP-FPM for PHP). Optimizing Nginx is crucial for overall system responsiveness.

Nginx Configuration Tuning

The primary configuration file is typically located at /etc/nginx/nginx.conf. We’ll focus on key directives within the http block and within your specific server block.

Global Nginx Settings

These settings affect the Nginx worker processes. Adjusting worker_processes to match your CPU cores is a common starting point. worker_connections dictates the maximum number of simultaneous connections a worker can handle.

user www-data;
worker_processes auto; # Set to the number of CPU cores, or 'auto'
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

    # Gzip compression for text-based assets
    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;

    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # SSL settings (if applicable)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    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';

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

    # Include virtual host configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Server Block Optimization

Within your site’s server block (e.g., /etc/nginx/sites-available/your_app), focus on caching, request handling, and proxying.

server {
    listen 80;
    listen [::]:80;
    server_name your_domain.com www.your_domain.com;

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

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your_domain.com www.your_domain.com;

    # SSL Certificate paths
    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;

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

    # Proxy to Gunicorn/PHP-FPM
    location / {
        proxy_pass http://unix:/run/gunicorn.sock; # Or http://127.0.0.1:8000; for TCP
        # For PHP-FPM:
        # include snippets/fastcgi-php.conf;
        # fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version

        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 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }

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

    # Error pages
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

Gunicorn Tuning for Python Applications

Gunicorn (Green Unicorn) is a Python WSGI HTTP Server. Its performance is heavily influenced by the number of worker processes and threads.

Worker Processes and Threads

The general recommendation is to set the number of worker processes to (2 * Number of CPU Cores) + 1. For threaded workers (using the gthread worker type), you can further tune the number of threads per worker.

# Example command to start Gunicorn
gunicorn --workers 3 \
         --worker-class gthread \
         --threads 2 \
         --bind unix:/run/gunicorn.sock \
         --timeout 120 \
         your_app.wsgi:application

# Explanation:
# --workers 3: (2 * 1 CPU core) + 1 = 3 workers
# --worker-class gthread: Uses threads for concurrency within each worker process.
# --threads 2: Each worker process will have 2 threads.
# --bind unix:/run/gunicorn.sock: Binds to a Unix socket for faster inter-process communication with Nginx.
# --timeout 120: Sets the worker timeout to 120 seconds. Adjust based on your application's longest-running requests.

For CPU-bound tasks, the default sync worker class might be sufficient, but for I/O-bound applications, gevent or gthread with multiple threads can offer better concurrency. If using gevent, ensure you have it installed (`pip install gevent`).

# Example with gevent
gunicorn --workers 3 \
         --worker-class gevent \
         --bind unix:/run/gunicorn.sock \
         --timeout 120 \
         your_app.wsgi:application

Systemd Service File for Gunicorn

Managing Gunicorn with systemd is standard practice for production environments.

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

[Service]
User=your_user
Group=www-data
WorkingDirectory=/path/to/your_app
ExecStart=/path/to/your_virtualenv/bin/gunicorn \
          --workers 3 \
          --worker-class gthread \
          --threads 2 \
          --bind unix:/run/gunicorn.sock \
          --timeout 120 \
          your_app.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Save this as /etc/systemd/system/gunicorn.service and enable/start it with:

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

PHP-FPM Tuning for PHP Applications

PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with web servers like Nginx. Its performance hinges on the process manager settings.

Process Manager Configuration

The configuration file is typically found at /etc/php/X.Y/fpm/pool.d/www.conf (replace X.Y with your PHP version, e.g., 8.1).

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock ; Or a TCP socket like 127.0.0.1:9000
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 50      ; Max number of children at any one time
pm.start_servers = 5      ; Number of children created at startup
pm.min_spare_servers = 2  ; Number of children to keep active at minimum
pm.max_spare_servers = 10 ; Number of children to keep active at maximum
pm.process_idle_timeout = 10s ; Timeout for idle processes to be killed

; For CPU-bound applications, consider 'ondemand' or 'static'
; pm = ondemand
; pm.max_children = 50
; pm.process_idle_timeout = 10s

; pm = static
; pm.max_children = 50

request_terminate_timeout = 120 ; Max execution time for a script
request_slowlog_timeout = 30s   ; Log scripts that take longer than this
slowlog = /var/log/php/php8.1-fpm.slow.log

; Other useful settings
; php_admin_value[memory_limit] = 256M
; php_admin_value[upload_max_filesize] = 64M
; php_admin_value[post_max_size] = 64M

Understanding PM settings:

  • dynamic: The most common setting. PHP-FPM starts with pm.start_servers, and dynamically spawns/kills processes based on load, staying within pm.min_spare_servers and pm.max_spare_servers, up to pm.max_children.
  • static: PHP-FPM keeps a fixed number of processes running (pm.max_children). This can be faster if you have consistent high traffic but wastes resources during low traffic.
  • ondemand: Processes are only spawned when a request is received and killed after a period of inactivity (pm.process_idle_timeout). This saves resources but can introduce slight latency on the first request after idle periods.

The optimal values for pm.max_children, pm.start_servers, etc., depend heavily on your server’s RAM and the memory footprint of your PHP application. A good starting point for pm.max_children is (Total RAM - RAM for OS/Nginx/DB) / Average PHP Process Memory. Monitor your server’s memory usage and adjust accordingly.

MongoDB Performance Tuning

MongoDB performance is critical for applications that rely on it as a primary data store. Tuning involves configuration, indexing, and query optimization.

MongoDB Configuration (`mongod.conf`)

The primary configuration file is usually /etc/mongod.conf.

storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
  engine: wiredTiger # Default and recommended storage engine

# network:
#   bindIp: 127.0.0.1 # Bind to localhost only for security if not accessed remotely

# processManagement:
#   fork: true
#   pidFilePath: /var/run/mongodb/mongod.pid
#   logFilePath: /var/log/mongodb/mongod.log

# systemLog:
#   destination: file
#   path: /var/log/mongodb/mongod.log
#   logAppend: true
#   verbosity: 0

# security:
#   authorization: enabled # Highly recommended for production

# sharding:
#   clusterRole: configsvr
#   configsvrFilePattern: /var/lib/mongodb/cs.metadata.json
#   configsvrDataDir: /var/lib/mongodb/cs

# replication:
#   oplogSizeMB: 10240 # Adjust oplog size for replica sets

# WiredTiger specific settings (can be tuned further)
# wiredTiger:
#   engineConfig:
#     cacheSizeGB: 0.5 # Example: Allocate 50% of available RAM to WiredTiger cache, adjust as needed
#   collectionConfig:
#     blockCompressor: snappy # Or zlib, zstd for better compression at CPU cost
#   indexConfig:
#     prefixCompression: true

Key Tuning Points:

  • storage.engine: wiredTiger is the default and generally the best choice.
  • storage.wiredTiger.engineConfig.cacheSizeGB: This is crucial. Allocate a significant portion of your server’s RAM to the WiredTiger cache. A common recommendation is 50% of available RAM, but monitor usage.
  • storage.wiredTiger.collectionConfig.blockCompressor: snappy offers a good balance of compression and CPU usage. zlib or zstd offer higher compression but use more CPU.
  • replication.oplogSizeMB: If running a replica set, ensure the oplog is large enough to accommodate write operations between elections or network interruptions.
  • security.authorization: Always enable authentication in production.

Indexing Strategy

Proper indexing is paramount for MongoDB query performance. Use explain() to analyze query performance and identify missing indexes.

// Example: Analyzing a find query
db.collection.find({ status: "active", type: "user" }).explain("executionStats")

// If the output shows 'COLLSCAN' (Collection Scan) or high 'totalKeysExamined' vs 'totalDocsExamined',
// an index is likely needed.

// Creating a compound index
db.collection.createIndex({ status: 1, type: 1 })

// Creating a text index for full-text search
db.collection.createIndex({ title: "text", body: "text" })

// Dropping an index
db.collection.dropIndex("index_name")

Consider the order of fields in compound indexes. MongoDB uses indexes from left to right. Place fields with higher cardinality (more unique values) earlier in the index.

Query Optimization

Avoid fetching more data than necessary. Use projection to limit returned fields.

// Fetching only specific fields
db.collection.find({ status: "active" }, { name: 1, email: 1, _id: 0 })
// This query returns only 'name' and 'email', excluding '_id'

Be mindful of aggregation pipeline stages. Some stages are more resource-intensive than others. Ensure stages like $match are placed as early as possible to filter documents before expensive operations.

Putting It All Together: Linode Deployment Workflow

When deploying or updating your application on Linode, follow a structured workflow:

  • Provision Linode Instance: Choose an instance size appropriate for your expected load.
  • Install Dependencies: Install Nginx, PHP (if applicable), Gunicorn (if applicable), Python, MongoDB, and any other required packages.
  • Configure MongoDB: Edit /etc/mongod.conf, restart MongoDB, and ensure it’s running.
  • Configure PHP-FPM (if applicable): Edit www.conf, restart PHP-FPM.
  • Configure Gunicorn (if applicable): Create/edit the systemd service file, start Gunicorn.
  • Configure Nginx: Edit nginx.conf and your site’s server block.
  • Test Nginx Configuration: Run sudo nginx -t.
  • Reload/Restart Nginx: sudo systemctl reload nginx or sudo systemctl restart nginx.
  • Monitor Performance: Use tools like htop, iotop, Nginx status module, PHP-FPM status page, MongoDB’s mongostat and mongotop, and application-level monitoring to identify bottlenecks.
  • Iterate and Tune: Based on monitoring, adjust configurations (e.g., worker counts, cache sizes, timeouts) and re-test.

This comprehensive approach to tuning Nginx, your application server (Gunicorn/PHP-FPM), and your database (MongoDB) on Linode will provide a robust and performant foundation for your web applications.

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 indexing lock conflicts and high CPU during bulk stock updates on DigitalOcean Servers
  • How to Debug and Fix memory leaks and socket exhaustion in daemon processes in Modern C++ Applications
  • Infrastructure as Code: Provisioning Secure PHP Clusters on DigitalOcean Using Terraform
  • Fixing Slow Largest Contentful Paint (LCP) caused by unoptimized database queries in Legacy Laravel Codebases Without Breaking API Contracts
  • An Auditor’s Checklist for Securing Laravel Backends on Google Cloud

Copyright © 2026 · Vinay Vengala