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

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

Nginx Configuration for WordPress Performance

Optimizing Nginx is crucial for serving WordPress efficiently. We’ll focus on caching, worker processes, and request handling.

Nginx Worker Processes and Connections

The worker_processes directive controls how many worker processes Nginx spawns. Setting this to auto is generally recommended, allowing Nginx to detect the number of CPU cores. The worker_connections directive defines the maximum number of simultaneous connections a worker process can handle. A common starting point is 1024, but this can be increased based on server load and available RAM.

Nginx Caching Strategies

Leveraging Nginx’s FastCGI cache for PHP-FPM output significantly reduces the load on your application and database. This involves defining cache zones and specifying cache behavior.

Configuring FastCGI Cache

First, define your cache zone in the http block. The keys_zone directive names the zone and allocates memory for keys. max_size sets the maximum disk space for the cache, and inactive specifies how long an item can remain in the cache without being accessed before being removed.

Example Nginx http block configuration

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

    # FastCGI Cache settings
    fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wp_cache:100m max_size=10g inactive=60m use_temp_path=off;
    fastcgi_temp_path /var/tmp/nginx/fastcgi_temp; # Ensure this directory exists and is writable by nginx user

    # ... other http settings ...
}

Enabling Cache in Server Block

Within your WordPress site’s server block, you’ll enable the cache and define cache zones for specific URIs. The fastcgi_cache_key directive is critical for ensuring unique cache entries. We’ll also set cache validity periods and bypass the cache for logged-in users or specific query parameters.

Example Nginx server block configuration

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;
    root /var/www/your_domain.com/public_html;
    index index.php index.html index.htm;

    # Enable FastCGI cache
    set $fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache wp_cache;
    fastcgi_cache_valid 200 60m; # Cache for 60 minutes for successful responses
    fastcgi_cache_valid 301 302 10m; # Cache redirects for 10 minutes
    fastcgi_cache_valid 404 1m; # Cache 404s for 1 minute
    fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_cache_revalidate on;
    fastcgi_cache_lock on;

    # Bypass cache for logged-in users or specific cookies
    map $http_cookie $skip_cache {
        default 0;
        "~*wordpress_logged_in" 1;
        "~*comment_author" 1;
    }

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

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

        # Add cache headers for debugging
        add_header X-Cache-Status $upstream_cache_status;
    }

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

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

Gunicorn/PHP-FPM Tuning

The application server (Gunicorn for Python-based CMSs, or PHP-FPM for WordPress) is the next critical layer. Proper tuning here ensures that requests are processed efficiently without overwhelming the server.

PHP-FPM Configuration

PHP-FPM’s performance is largely dictated by its process manager settings. The pm directive can be set to static, dynamic, or ondemand. For WordPress, dynamic or ondemand are often good choices, balancing resource usage with responsiveness.

PHP-FPM Pool Configuration (www.conf)

Locate your PHP-FPM pool configuration file (e.g., /etc/php/7.4/fpm/pool.d/www.conf). Key directives to tune include:

  • pm.max_children: The maximum number of child processes that will be spawned. This is a hard limit.
  • pm.start_servers: The number of child processes to start when PHP-FPM starts.
  • pm.min_spare_servers: The minimum number of idle (spare) processes that should be kept active.
  • pm.max_spare_servers: The maximum number of idle (spare) processes that should be kept active.
  • pm.process_idle_timeout: The number of seconds after which a child process will be killed if it is idle.
  • request_terminate_timeout: The number of seconds after which a script will be killed. Useful for preventing runaway scripts.

Tuning pm.max_children

A common formula to estimate pm.max_children is: (Total RAM - RAM used by OS and other services) / RAM per PHP process. You can monitor RAM usage per PHP process using tools like htop or by analyzing ps aux | grep php-fpm. Start conservatively and increase as needed, monitoring server stability.

Example PHP-FPM Pool Configuration (Dynamic PM)

; /etc/php/7.4/fpm/pool.d/www.conf

[www]
user = www-data
group = www-data
listen = /var/run/php/php7.4-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process Manager settings
pm = dynamic
pm.max_children = 50        ; Adjust based on server RAM and typical load
pm.start_servers = 5        ; Initial number of processes
pm.min_spare_servers = 2    ; Minimum idle processes
pm.max_spare_servers = 10   ; Maximum idle processes
pm.process_idle_timeout = 10s ; Timeout for idle processes
pm.max_requests = 500       ; Restart child processes after this many requests

; Request termination timeout
request_terminate_timeout = 60s

; Other settings
chdir = /
catch_workers_output = yes
; php_admin_value[error_log] = /var/log/php/php7.4-fpm.log
; php_admin_flag[log_errors] = on
; php_admin_value[memory_limit] = 256M ; Adjust as needed for your WordPress site

PostgreSQL Tuning for WordPress

PostgreSQL, while robust, also benefits from careful tuning, especially under heavy WordPress load. Key areas include shared memory, WAL (Write-Ahead Logging), and connection pooling.

Shared Memory and Buffers

shared_buffers is arguably the most critical parameter. It determines how much memory PostgreSQL uses to cache data. A common recommendation is 25% of system RAM, but this can be adjusted based on your workload and available memory. work_mem controls the amount of memory used for internal sort operations and hash tables before writing to temporary files. For WordPress, especially with complex queries or plugins, increasing this can be beneficial.

Write-Ahead Logging (WAL)

Tuning WAL parameters can improve write performance. wal_buffers is the amount of memory used for WAL data before writing to disk. min_wal_size and max_wal_size control the amount of WAL files that can accumulate before archiving or recycling. Increasing these can reduce I/O pressure during periods of heavy writes.

Connection Pooling

WordPress applications often create many short-lived database connections. Using a connection pooler like PgBouncer can significantly reduce overhead by reusing existing connections. This is especially important on busy sites or when using PHP-FPM with a high number of max_children.

PostgreSQL Configuration (postgresql.conf)

Edit your postgresql.conf file (typically found in /etc/postgresql/[version]/main/). Remember to restart PostgreSQL after making changes.

Example postgresql.conf Tuning

# PostgreSQL configuration file
# For more details, see: https://www.postgresql.org/docs/current/runtime-config.html

# General Configuration
datestyle = 'iso, mdy'
timezone = 'UTC'
lc_messages = 'en_US.UTF-8'
lc_numeric = 'en_US.UTF-8'
server_encoding = 'UTF8'

# Memory and Buffers
shared_buffers = 2GB             ; Adjust based on 25% of system RAM
work_mem = 64MB                  ; Increase for complex queries, adjust per workload
maintenance_work_mem = 512MB     ; For VACUUM, ANALYZE, etc.
effective_cache_size = 6GB       ; Estimate of total available cache (OS + shared_buffers)

# WAL Configuration
wal_level = replica              ; Or logical if needed for replication
wal_buffers = 16MB               ; Typically 1/3 of shared_buffers, but capped
min_wal_size = 4GB               ; Increase to reduce I/O during writes
max_wal_size = 16GB              ; Increase to reduce I/O during writes
checkpoint_completion_target = 0.9 ; Spread checkpoints over time
wal_sync_method = fsync          ; Or open_sync, fdatasync depending on OS and hardware

# Connection Settings
max_connections = 100            ; Adjust based on expected concurrent users and PgBouncer usage
; listen_addresses = '*'         ; Uncomment and configure if not using a local PgBouncer

# Autovacuum settings
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 15s
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50

# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_statement = 'none'           ; Consider 'ddl' or 'all' for debugging, but can be verbose
log_min_duration_statement = 250ms ; Log queries longer than 250ms
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0               ; Log temp files larger than this size (0 to disable)
log_autovacuum_min_duration = 10s ; Log autovacuum actions taking longer than 10s

PgBouncer Configuration (Example)

Install and configure PgBouncer. The pgbouncer.ini file is key. We’ll use a transaction pooling mode for WordPress, which is generally safe and efficient.

Example pgbouncer.ini

[databases]
; Database connection string for your WordPress DB
your_db_name = host=127.0.0.1 port=5432 dbname=your_db_name user=your_db_user password=your_db_password

[pgbouncer]
; Listen address and port
listen_addr = 127.0.0.1
listen_port = 6432

; Pooling mode: session, transaction, or statement
; Transaction mode is generally recommended for WordPress
pool_mode = transaction

; Maximum number of clients per server connection
default_pool_size = 20

; Maximum number of server connections for each database
max_db_connections = 100

; Database list (must match the [databases] section)
; The format is "database_name = connection_string"
; Example:
; mydatabase = host=localhost port=5432 dbname=mydb user=myuser password=mypass

; Authentication method (e.g., md5, scram-sha-256, trust)
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt ; Path to userlist file

; Log settings
logfile = /var/log/pgbouncer/pgbouncer.log
; loglevel = 2 ; 0=none, 1=error, 2=warn, 3=info, 4=debug

; Other settings
; pidfile = /var/run/pgbouncer/pgbouncer.pid
; admin_users = pgbouncer_admin
; stats_users = stats_user
; server_reset_query = DISCARD ALL;

Example userlist.txt

"your_db_user" "your_db_password"

After configuring PgBouncer, update your WordPress wp-config.php to connect to PgBouncer’s port (e.g., 6432) instead of directly to PostgreSQL.

Monitoring and Iteration

Performance tuning is an iterative process. Regularly monitor your server’s resource utilization (CPU, RAM, I/O), Nginx access logs, PHP-FPM logs, and PostgreSQL logs. Tools like htop, iotop, pg_stat_activity, and Nginx’s stub_status module are invaluable. Use tools like ApacheBench (ab) or k6 for load testing to validate your changes under simulated traffic.

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

  • 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
  • Rust Tokio async/await vs. Node.js Event Loop: Event-Driven Concurrency and CPU Yielding Models
  • Spring Boot (Java) vs. Axum (Rust): Optimizing for Low-Latency Financial Transaction APIs
  • FastAPI (Python) vs. Go Gin: CPU vs. I/O Performance in Real-World REST Endpoints

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (583)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (961)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (804)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (18)
  • 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

  • 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
  • Rust Tokio async/await vs. Node.js Event Loop: Event-Driven Concurrency and CPU Yielding Models
  • Spring Boot (Java) vs. Axum (Rust): Optimizing for Low-Latency Financial Transaction APIs
  • FastAPI (Python) vs. Go Gin: CPU vs. I/O Performance in Real-World REST Endpoints
  • Rust Axum vs. Go Fiber: HTTP/2 Multiplexing and Peak Connection Benchmarking

Top Categories

  • DevOps & Cloud Scaling (961)
  • Performance & Optimization (804)
  • Debugging & Troubleshooting (583)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

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