• 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 PostgreSQL on Linode for WordPress

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on Linode for WordPress

Nginx as a High-Performance Frontend for WordPress

When deploying WordPress with a modern stack, Nginx serves as an exceptional frontend, efficiently handling static assets, SSL termination, and request routing to your application server (Gunicorn for Python-based CMS, or PHP-FPM for traditional PHP). Tuning Nginx is paramount for maximizing throughput and minimizing latency.

Core Nginx Configuration Tuning

The primary configuration file, typically located at /etc/nginx/nginx.conf, contains global settings. Key directives to optimize include:

  • worker_processes: Set this to the number of CPU cores available. On a Linode instance, this is easily discoverable.
  • worker_connections: The maximum number of simultaneous connections each worker process can handle. A common starting point is 1024 or higher, depending on expected load.
  • multi_accept: Set to on to allow workers to accept multiple connections at once.
  • keepalive_timeout: Controls how long an idle HTTP connection will remain open. A value between 15 and 65 seconds is typical.
  • sendfile: Set to on to enable efficient file transfer by copying data directly from the kernel’s page cache to the socket.
  • tcp_nopush and tcp_nodelay: Set to on to optimize packet transmission.

Here’s an example of a tuned nginx.conf snippet:

worker_processes auto; # Or set to the number of CPU cores
worker_connections 4096; # Adjust based on expected load
multi_accept on;

events {
    use epoll; # For Linux systems
}

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

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    keepalive_timeout  65;
    keepalive_requests 1000; # Max requests per keepalive connection

    # 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 image/svg+xml;

    # Caching for static assets
    location ~* \.(jpg|jpeg|gif|png|webp|svg|css|js|ico|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    # ... other server blocks ...
}

WordPress-Specific Nginx Configuration

For WordPress, the server block needs careful configuration to route requests appropriately and handle PHP processing. If you’re using PHP-FPM, Nginx will proxy requests to it. If you’re using Gunicorn (e.g., with a Python-based WordPress alternative or a custom setup), the proxy configuration will differ.

Nginx with PHP-FPM

This is the most common setup for traditional PHP WordPress. The location ~ \.php$ block is critical.

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;
    root /var/www/html/wordpress; # Adjust to your WordPress installation path
    index index.php index.html index.htm;

    # SSL Configuration (if applicable)
    # listen 443 ssl http2;
    # 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;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # With php-fpm (or other unix sockets):
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version and socket path
        # Or using TCP/IP:
        # fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

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

    # Static file caching (already defined globally, but can be overridden)
    location ~* \.(jpg|jpeg|gif|png|webp|svg|css|js|ico|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Nginx with Gunicorn (for Python-based CMS)

If you’re using a Python framework like Django or Flask for your CMS, Gunicorn will be your WSGI HTTP Server. Nginx will proxy requests to Gunicorn, typically via a Unix socket or TCP port.

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;
    root /path/to/your/python/cms/public; # Adjust to your CMS's static file root
    index index.html index.htm;

    # Static file serving (handled by Nginx for performance)
    location /static/ {
        alias /path/to/your/python/cms/static/; # Adjust path
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location /media/ {
        alias /path/to/your/python/cms/media/; # Adjust path
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    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 via Unix Socket
        proxy_pass http://unix:/run/gunicorn.sock; # Adjust socket path

        # Or proxy to Gunicorn via TCP/IP
        # proxy_pass http://127.0.0.1:8000;
    }
}

Gunicorn Tuning for WordPress (Python-based)

Gunicorn (Green Unicorn) is a Python WSGI HTTP Server commonly used to run Python web applications. Tuning Gunicorn involves adjusting its worker processes and threads to match your server’s resources and application’s I/O characteristics.

Gunicorn Worker Configuration

The most impactful settings are:

  • --workers: The number of worker processes. A common recommendation is (2 * CPU_CORES) + 1.
  • --threads: The number of threads per worker. This is crucial for I/O-bound applications. For WordPress-like workloads (which can be I/O bound due to database queries and file access), using threads can significantly increase concurrency without the overhead of additional processes.
  • --worker-connections: (Deprecated in newer Gunicorn versions, replaced by threads) Maximum number of clients a worker can serve simultaneously.
  • --worker-class: The type of worker. sync is the default and simplest. gevent or eventlet are asynchronous workers that can handle more concurrent connections with fewer resources, especially beneficial for I/O-bound tasks.

Here’s an example of how you might start Gunicorn:

# Example for a 4-core CPU server, using gevent for async I/O
gunicorn --workers 9 --worker-class gevent --threads 2 --bind unix:/run/gunicorn.sock your_project.wsgi:application --log-level info --log-file /var/log/gunicorn/your_project.log

Note: The --threads option is only applicable when using worker classes that support threading (like sync). Asynchronous workers like gevent manage concurrency differently, and the concept of threads per worker is less direct. For gevent, focus on the number of workers and ensure your application code is gevent-compatible.

Gunicorn Timeout and Keep-Alive

Adjusting timeouts is important to prevent workers from being killed prematurely during long-running operations, but also to free up workers from stuck requests.

  • --timeout: The number of seconds to wait for a worker to respond. Default is 30. Increase if you have known long-running operations, but be cautious.
  • --keep-alive: The number of seconds to wait for a new request on a keep-alive connection.
# Example with increased timeout
gunicorn --workers 9 --worker-class gevent --threads 2 --timeout 120 --bind unix:/run/gunicorn.sock your_project.wsgi:application

PHP-FPM Tuning for WordPress

PHP-FPM (FastCGI Process Manager) is the de facto standard for running PHP applications with Nginx. Its performance is heavily influenced by its process management and resource allocation.

PHP-FPM Pool Configuration

PHP-FPM pools are configured in files typically found in /etc/php/X.Y/fpm/pool.d/www.conf (where X.Y is your PHP version). Key directives include:

  • pm: Process manager control. Options are static, dynamic, and ondemand.
    • static: Keeps a fixed number of child processes running. Good for predictable loads and dedicated servers.
    • dynamic: Starts processes as needed, up to a maximum.
    • ondemand: Starts processes only when a request comes in.
  • pm.max_children: The maximum number of child processes that will be spawned. This is the most critical setting for memory usage.
  • pm.start_servers: The number of child processes to start when PHP-FPM starts. (For dynamic)
  • pm.min_spare_servers: The minimum number of “spare” (idle) processes. (For dynamic)
  • pm.max_spare_servers: The maximum number of “spare” (idle) processes. (For dynamic)
  • pm.max_requests: The number of requests each child process will execute before respawning. This helps prevent memory leaks.

Recommendation: For WordPress, a static process manager is often preferred for consistent performance, especially on servers with sufficient RAM. If memory is constrained, dynamic can be a good compromise.

; Example for a server with 8GB RAM and 16 CPU cores
; Using static process management for predictable performance
pm = static
pm.max_children = 150 ; Adjust based on available RAM and typical WordPress memory usage per process
pm.max_requests = 500 ; Helps mitigate memory leaks over time

; If using dynamic, consider these:
; pm = dynamic
; pm.max_children = 150
; pm.start_servers = 10
; pm.min_spare_servers = 5
; pm.max_spare_servers = 20
; pm.max_requests = 500

; Other important settings
request_terminate_timeout = 60s ; Timeout for individual requests
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen = /var/run/php/php7.4-fpm.sock ; Ensure this matches your Nginx config
user = www-data
group = www-data

After modifying www.conf, you must restart PHP-FPM:

sudo systemctl restart php7.4-fpm # Adjust PHP version as needed

PHP Settings for WordPress

Beyond FPM configuration, core PHP settings in php.ini (e.g., /etc/php/X.Y/fpm/php.ini) also impact performance:

  • memory_limit: Ensure this is sufficient for WordPress and its plugins (e.g., 256M or 512M).
  • max_execution_time: The maximum time in seconds a script is allowed to run. Default is 30. Increase cautiously if needed for specific operations.
  • upload_max_filesize and post_max_size: Important for media uploads.
  • opcache.enable and opcache.memory_consumption: Essential for performance. Ensure OPcache is enabled and has adequate memory allocated (e.g., 128M or 256M).
[OPcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256 ; MB
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.validate_timestamps=1
opcache.save_comments=1
opcache.enable_file_override=0

[PHP]
memory_limit = 512M
max_execution_time = 120
upload_max_filesize = 64M
post_max_size = 64M

PostgreSQL Tuning for WordPress Database

PostgreSQL is a robust database system. Optimizing its configuration, particularly postgresql.conf, is crucial for WordPress performance, as the database is often the bottleneck.

Key PostgreSQL Configuration Parameters

Locate your postgresql.conf file (path varies by OS and version, often /etc/postgresql/X.Y/main/postgresql.conf). Key parameters to tune:

  • shared_buffers: The amount of memory dedicated to PostgreSQL’s shared buffer cache. A common starting point is 25% of system RAM, but can be pushed higher (up to 40-50%) on dedicated database servers.
  • work_mem: The amount of memory used for internal sort operations and hash tables before disk spills occur. This is per operation, so be conservative.
  • maintenance_work_mem: Memory used for maintenance operations like VACUUM, CREATE INDEX, and ALTER TABLE.
  • effective_cache_size: An estimate of how much memory is available for disk caching by the operating system and PostgreSQL’s shared buffers.
  • wal_buffers: Memory for WAL (Write-Ahead Logging) data.
  • checkpoint_completion_target: Spreads WAL checkpointing over time, reducing I/O spikes.
  • max_connections: Maximum number of concurrent connections.

Here’s an example snippet for a Linode instance with 16GB RAM:

# Example for a 16GB RAM server
shared_buffers = 4GB ; ~25% of RAM
work_mem = 64MB ; Adjust based on query complexity and RAM
maintenance_work_mem = 512MB ; For VACUUM, index creation etc.
effective_cache_size = 12GB ; OS cache + shared_buffers
wal_buffers = 16MB
checkpoint_completion_target = 0.9
max_wal_size = 4GB ; Replaces wal_level and related settings in newer versions
min_wal_size = 1GB
max_connections = 200 ; Adjust based on application needs and server resources
random_page_cost = 1.1 ; Lower for SSDs
seq_page_cost = 1.0

After modifying postgresql.conf, you must reload or restart the PostgreSQL service:

sudo systemctl reload postgresql # Or restart if reload is not sufficient

PostgreSQL Vacuuming and Indexing

Regular maintenance is critical. WordPress databases can become bloated with post revisions, transients, and orphaned metadata. Automated vacuuming and proper indexing are key.

  • Autovacuum: Ensure autovacuum is enabled and tuned. The default settings are often too conservative. You might need to adjust autovacuum_vacuum_scale_factor and autovacuum_analyze_scale_factor.
  • Index Analysis: Regularly analyze your database schema for missing or redundant indexes. Tools like pg_stat_statements and EXPLAIN ANALYZE are invaluable.
  • WordPress Plugins: Be mindful of plugins that create excessive database load or bloat (e.g., poorly optimized theme options, analytics plugins, or revision management plugins). Consider using plugins like WP-Optimize for database cleanup.

Monitoring and Iterative Tuning

Performance tuning is not a one-time event. Continuous monitoring and iterative adjustments are essential. Use tools like:

  • Nginx: nginx -s reload, /var/log/nginx/access.log, /var/log/nginx/error.log, Nginx Amplify, or Prometheus/Grafana with the Nginx exporter.
  • PHP-FPM: Status page (if enabled), /var/log/phpX.Y-fpm.log, New Relic, or Blackfire.io.
  • PostgreSQL: pg_stat_activity, pg_stat_statements, EXPLAIN ANALYZE, pgBadger, Prometheus/Grafana with the node_exporter and postgres_exporter.
  • System: top, htop, vmstat, iostat, Linode’s built-in monitoring.

Start with conservative settings, monitor the impact, and gradually increase limits or adjust parameters based on observed performance metrics and resource utilization. Always test changes in a staging environment before deploying to production.

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

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

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

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • 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