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

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

Nginx as a High-Performance Frontend Proxy

For a robust web application stack, Nginx serves as an exceptional frontend proxy, efficiently handling static assets, SSL termination, and load balancing requests to your application servers. Tuning Nginx is crucial for maximizing throughput and minimizing latency.

We’ll focus on key directives within nginx.conf, typically located at /etc/nginx/nginx.conf or within /etc/nginx/conf.d/.

Worker Processes and Connections

The worker_processes directive determines how many worker processes Nginx will spawn. Setting this to auto is generally recommended, allowing Nginx to detect the number of CPU cores and utilize them effectively. The worker_connections directive sets the maximum number of simultaneous connections that each worker process can handle. This value should be set high enough to accommodate your expected traffic, considering that each connection consumes a file descriptor.

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

events {
    worker_connections 1024; # Adjust based on system limits and expected load
    multi_accept on;
}

Tuning Tip: The maximum number of open file descriptors for a process is controlled by ulimit -n. Ensure this limit is set sufficiently high (e.g., 65536 or higher) for the Nginx user. You can check this with ulimit -n and set it persistently in /etc/security/limits.conf.

HTTP Core Settings

Directives like keepalive_timeout and send_timeout influence how long Nginx maintains persistent connections and how long it waits for data from the client. Optimizing these can reduce overhead.

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;

    # ... other http configurations
}

Explanation:

  • sendfile on;: Allows Nginx to send files directly from the kernel’s page cache, bypassing user space and improving performance for static file delivery.
  • tcp_nopush on;: Instructs Nginx to try and send HTTP response headers in one packet, along with any preceding data, reducing the number of packets sent.
  • tcp_nodelay on;: Disables the Nagle algorithm, which can improve latency by sending small packets immediately.
  • keepalive_timeout 65;: Sets the timeout for keep-alive connections. A value around 60-75 seconds is often a good balance.

Gzip Compression

Enabling Gzip compression significantly reduces the bandwidth required for transferring text-based assets (HTML, CSS, JS, JSON). This is a low-CPU cost operation with high network savings.

http {
    # ... other http configurations

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6; # Compression level (1-9, 6 is a good balance)
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # ... rest of http configuration
}

Caching

Leverage browser caching and Nginx’s proxy caching to reduce load on your application servers and speed up responses for repeat visitors.

http {
    # ... other http configurations

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

    # Proxy Caching (example for API responses)
    proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m;

    location /api/ {
        proxy_pass http://your_app_backend;
        proxy_cache api_cache;
        proxy_cache_valid 200 302 10m; # Cache successful responses for 10 minutes
        proxy_cache_valid 404 1m;      # Cache 404s for 1 minute
        add_header X-Cache-Status $upstream_cache_status;
    }

    # ... rest of http configuration
}

Note: Ensure the cache directory (e.g., /var/cache/nginx/api) exists and Nginx has write permissions: sudo mkdir -p /var/cache/nginx/api && sudo chown www-data:www-data /var/cache/nginx/api.

Gunicorn/FPM: Application Server Tuning

The choice between Gunicorn (for Python/WSGI) and PHP-FPM (for PHP) dictates how your application server is configured. Both aim to manage worker processes that execute your application code.

Gunicorn (Python WSGI)

Gunicorn is a popular WSGI HTTP Server for Python. Its configuration heavily relies on the number of worker processes and threads.

A common starting point for Gunicorn is to use the --workers flag. A good rule of thumb is (2 * number_of_cores) + 1. For I/O-bound applications, you might consider using --threads in conjunction with workers, but this adds complexity and potential for race conditions if not handled carefully. For CPU-bound tasks, sticking to workers is often simpler and more predictable.

# Example command line for Gunicorn
gunicorn --workers 3 --bind 0.0.0.0:8000 myapp.wsgi:application

# Or using a Gunicorn configuration file (e.g., gunicorn_config.py)
# workers = 3
# bind = "0.0.0.0:8000"
# module = "myapp.wsgi:application"
# gunicorn -c gunicorn_config.py

Tuning Gunicorn:

  • Worker Type: The default is sync. For I/O-bound applications, consider gevent or eventlet for asynchronous handling, which can improve concurrency without needing many threads.
  • --worker-connections (for async workers): If using gevent or eventlet, this defines the maximum number of simultaneous connections per worker.
  • --threads: Use with caution. If your application is not thread-safe, this can lead to bugs. It’s often better to scale horizontally or use async workers.
  • --max-requests: Set a limit on the number of requests a worker can handle before it’s restarted. This helps prevent memory leaks from accumulating over time. A value like 1000 or 5000 is common.
  • --timeout: The number of seconds Gunicorn will wait at maximum for a worker to process a request. Adjust this based on your slowest expected API calls.
# Example with max_requests and timeout
gunicorn --workers 3 --threads 2 --max-requests 1000 --timeout 30 --bind 0.0.0.0:8000 myapp.wsgi:application

PHP-FPM

PHP-FPM (FastCGI Process Manager) is the standard for running PHP applications. Its performance is governed by the pm (process manager) settings in its configuration file, typically /etc/php/X.Y/fpm/pool.d/www.conf.

The key directives are:

  • pm: Can be static, dynamic, or ondemand.
  • pm.max_children: The maximum number of child processes to be created when pm is set to static or dynamic.
  • pm.start_servers: The number of child processes to start when the FPM master process starts.
  • pm.min_spare_servers: The desired minimum number of idle supervisor processes.
  • pm.max_spare_servers: The desired maximum number of idle supervisor processes.
  • pm.max_requests: The number of requests each child process will serve before re-spawning. Similar to Gunicorn’s --max-requests.
; Example www.conf settings for a moderate server (e.g., 2 CPU cores)
; Adjust these values based on your server's resources and traffic patterns

[www]
user = www-data
group = www-data
listen = /run/php/php7.4-fpm.sock ; Or a TCP socket like 127.0.0.1:9000

; Process Manager Settings
pm = dynamic
pm.max_children = 50       ; Max processes, adjust based on RAM
pm.start_servers = 5       ; Initial processes
pm.min_spare_servers = 2   ; Min idle processes
pm.max_spare_servers = 10  ; Max idle processes
pm.max_requests = 500      ; Restart after 500 requests to prevent leaks
pm.process_idle_timeout = 10s ; For 'ondemand' or 'dynamic' to free up idle workers

; Other settings
catch_workers_output = yes ; Log worker output to stderr/stdout
; php_admin_value[memory_limit] = 128M ; Example: set memory limit per process

Tuning PHP-FPM:

  • pm.max_children: This is the most critical setting. Too high, and you’ll run out of RAM. Too low, and you’ll have requests queued. A common starting point is to monitor RAM usage and set it such that pm.max_children * average_process_memory_usage is less than 70-80% of your total RAM.
  • pm.start_servers, min_spare_servers, max_spare_servers: These control how quickly PHP-FPM can respond to new requests by keeping a pool of idle workers ready.
  • pm.max_requests: Essential for long-running applications to mitigate memory leaks.
  • pm = ondemand: This mode starts no children until a request comes in. It saves memory but can introduce a slight delay on the first request after a period of inactivity. Useful for low-traffic sites.
  • pm = static: Keeps a fixed number of children running at all times. Good for high-traffic sites where predictable performance is key, but consumes more memory.

PostgreSQL Performance Tuning

PostgreSQL’s performance is heavily influenced by its configuration parameters, primarily managed in postgresql.conf. The key is to balance resource utilization (CPU, RAM, I/O) with query execution speed.

Key Configuration Parameters

Locate postgresql.conf (often in /etc/postgresql/X.Y/main/ or /var/lib/pgsql/X.Y/data/). Restart PostgreSQL after making changes: sudo systemctl restart postgresql.

# Shared Memory Buffers
shared_buffers = 256MB       # Typically 25% of total RAM is a good starting point.
                             # For servers with >16GB RAM, can go up to 40% if needed.

# WAL (Write-Ahead Logging) Buffers
wal_buffers = 16MB           # Usually -1 (auto-tuned) or 16MB is sufficient.

# Checkpoint Settings
checkpoint_completion_target = 0.9 # Spreads checkpoint writes over time.
checkpoint_timeout = 5min        # How often checkpoints occur. Adjust based on WAL volume.

# Background Writer
bgwriter_lru_maxpages = 1000     # Max pages bgwriter can write per iteration.
bgwriter_lru_multiplier = 1.0    # Controls how aggressively bgwriter cleans up.

# Query Planner
effective_cache_size = 1GB       # Estimate of total memory available for disk caching by OS and PostgreSQL.
                             # Typically 50-75% of total RAM.

# Connection Settings
max_connections = 100        # Adjust based on application needs and server resources.
                             # Each connection consumes RAM.

# Memory for Sorting and Hashing
work_mem = 16MB              # Memory for internal sort operations and hash tables.
                             # Increase for complex queries with large sorts/hashes.
                             # Be cautious: this is per operation, per connection.

# Maintenance Work Memory
maintenance_work_mem = 64MB  # Memory for VACUUM, CREATE INDEX, ALTER TABLE.
                             # Higher values speed up maintenance tasks.

# Autovacuum
autovacuum = on
autovacuum_max_workers = 3   # Number of autovacuum worker processes.
autovacuum_naptime = 1min    # How often to check for jobs.
autovacuum_vacuum_threshold = 50 # Min number of row updates/deletes before vacuum.
autovacuum_analyze_threshold = 50 # Min number of row inserts/updates/deletes before analyze.

Tuning PostgreSQL:

  • shared_buffers: This is arguably the most important parameter. Setting it too high can lead to double buffering (OS cache + PostgreSQL cache) and reduced performance. Start with 25% of RAM and monitor.
  • effective_cache_size: This informs the query planner about how much memory is available for caching data blocks. Setting it too low will make the planner favor less efficient index scans.
  • work_mem: Crucial for complex queries. If you see “Sort Method: external merge Disk” in EXPLAIN ANALYZE, increasing work_mem can help. However, be very careful as this memory is allocated per sort/hash operation within a query, and multiple such operations can occur concurrently.
  • maintenance_work_mem: Essential for efficient index creation and vacuuming. If you perform large data loads or schema changes, increasing this can significantly reduce downtime.
  • Autovacuum: Ensure autovacuum is enabled and tuned. It’s vital for reclaiming space from dead tuples and preventing table bloat, which degrades query performance. Monitor pg_stat_user_tables for n_dead_tup.
  • WAL Tuning: For write-heavy workloads, tuning max_wal_size and checkpoint_timeout can improve write throughput by reducing the frequency of checkpoints.

Monitoring and Iteration

Performance tuning is an iterative process. Use monitoring tools to observe the impact of your changes:

  • Nginx: stub_status module for active connections, requests per second.
  • Gunicorn/PHP-FPM: Process counts, memory usage, request latency.
  • PostgreSQL: pg_stat_activity for active queries, pg_stat_statements for query performance analysis, pg_buffercache for cache hit ratios, system monitoring for CPU, RAM, and I/O.

Tools like Prometheus with Node Exporter, Grafana for visualization, and specialized PostgreSQL monitoring tools (e.g., PMM, pgAdmin’s dashboards) are invaluable. Regularly analyze logs for errors and slow queries. Make one significant change at a time, measure its impact, and document the results.

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
  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH

Copyright © 2026 · Vinay Vengala