• 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 MySQL on AWS for C

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on AWS for C

Nginx as a High-Performance Frontend Proxy

When deploying web applications, especially those built with Python (using Gunicorn) or PHP (using PHP-FPM), Nginx serves as an indispensable frontend proxy. Its event-driven, asynchronous architecture makes it exceptionally efficient at handling a large number of concurrent connections, offloading static file serving, SSL termination, and load balancing. Tuning Nginx is crucial for maximizing throughput and minimizing latency.

Core Nginx Performance Directives

The primary configuration file for Nginx is typically located at /etc/nginx/nginx.conf. Within the main context (outside of http, server, or location blocks), several directives significantly impact performance:

Worker Processes and Connections

The worker_processes directive determines how many worker processes Nginx will spawn. A common recommendation is to set this to the number of CPU cores available on the server. The worker_connections directive sets the maximum number of simultaneous connections that each worker process can handle. The total maximum connections will be worker_processes * worker_connections.

Tuning Example (nginx.conf)

# In the main context (outside http block)
user www-data;
worker_processes auto; # Or set to number of CPU cores, e.g., worker_processes 4;

events {
    worker_connections 1024; # Adjust based on expected load and system limits
    multi_accept on; # Allows workers to accept multiple connections at once
}

multi_accept on; is particularly useful for high-concurrency scenarios, allowing workers to accept new connections more rapidly.

File Descriptors and Open Files Limit

Each connection in Nginx consumes a file descriptor. To avoid hitting system limits, especially under heavy load, it’s essential to increase the open files limit. This is typically done via ulimit settings, often configured in /etc/security/limits.conf or systemd service files.

System-wide Limits (limits.conf)

# In /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
root soft nofile 65536
root hard nofile 65536

After modifying limits.conf, you’ll need to either reboot the server or re-login for the changes to take effect for your user. For Nginx specifically, ensure the user Nginx runs as (e.g., www-data) has these limits applied. Often, this is managed by the systemd service file for Nginx.

Systemd Service Limits (e.g., /etc/systemd/system/nginx.service.d/override.conf)

[Service]
LimitNOFILE=65536

After creating or modifying this override file, reload systemd and restart Nginx: sudo systemctl daemon-reload && sudo systemctl restart nginx.

Keep-Alive Connections

HTTP keep-alive (persistent connections) significantly reduces the overhead of establishing new TCP connections for each request. Nginx’s keepalive_timeout and keepalive_requests directives control this behavior.

Tuning Example (nginx.conf – http block)

http {
    # ... other http directives ...

    keepalive_timeout 65; # Default is 75. Adjust based on client behavior and server load.
    keepalive_requests 100; # Default is 100. Maximum requests per keep-alive connection.

    # ... other http directives ...
}

A longer keepalive_timeout can be beneficial if clients tend to make multiple requests in quick succession, but it also ties up worker connections longer. A value between 30-75 seconds is a common starting point.

Buffering and Timeouts

Nginx uses buffers to handle data transfer between clients and backend servers. Tuning buffer sizes and timeouts can prevent issues with slow clients or slow backends.

Tuning Example (nginx.conf – http or server block)

http {
    # ...

    client_body_buffer_size 128k; # Default is 16k. Increase for large POST requests.
    client_header_buffer_size 1k; # Default is 1k. Usually sufficient.
    large_client_header_buffers 2 128k; # Default is 2 4k. For large headers.

    client_max_body_size 100M; # Maximum allowed size of the client request body.

    send_timeout 60s; # Timeout for sending a response to the client.
    client_body_timeout 60s; # Timeout for receiving client request body.
    client_header_timeout 60s; # Timeout for receiving client request header.

    proxy_connect_timeout 60s; # Timeout for establishing a connection with the upstream server.
    proxy_send_timeout 60s; # Timeout for transmitting a request to the upstream server.
    proxy_read_timeout 60s; # Timeout for receiving a response from the upstream server.

    # ...
}

client_max_body_size is critical for file uploads. proxy_read_timeout is particularly important when dealing with long-running backend processes.

Gunicorn Tuning for Python Applications

Gunicorn (Green Unicorn) is a popular WSGI HTTP Server for Python. It’s a pre-fork worker model, meaning it spawns multiple worker processes. Tuning Gunicorn involves selecting the right worker type and determining the optimal number of workers.

Worker Types

Gunicorn offers several worker types:

  • Sync Workers (sync): The default and most basic worker type. Each worker handles one request at a time. Suitable for I/O-bound applications that don’t use asynchronous libraries.
  • Asynchronous Workers (gevent, eventlet): These workers can handle multiple requests concurrently using non-blocking I/O. Ideal for applications that perform a lot of I/O operations (network requests, database queries) and can leverage asynchronous libraries.
  • Threaded Workers (gthread): Uses threads within a single process to handle multiple requests. Can be useful for CPU-bound tasks if the Global Interpreter Lock (GIL) is not a bottleneck, but generally less performant than async workers for I/O-bound tasks.

For most modern Python web applications, especially those making external API calls or database queries, gevent or eventlet workers are recommended for better concurrency.

Number of Workers

The general recommendation for the number of worker processes is:

  • For sync workers: 2 * Number of CPU Cores + 1.
  • For gevent/eventlet workers: This is more nuanced. A common starting point is Number of CPU Cores * 2 or even higher, as these workers can handle many concurrent connections efficiently. The key is to monitor CPU and memory usage and adjust.

Gunicorn Command Line / Configuration

You can configure Gunicorn via command-line arguments or a Python configuration file.

Example Command Line

# For sync workers
gunicorn --workers 3 --worker-class sync --bind 0.0.0.0:8000 myapp.wsgi:application

# For gevent workers (requires gevent installed: pip install gevent)
gunicorn --workers 4 --worker-class gevent --bind 0.0.0.0:8000 myapp.wsgi:application

Example Configuration File (gunicorn_config.py)

import multiprocessing

# Number of worker processes
workers = multiprocessing.cpu_count() * 2 + 1
# Or for gevent/eventlet, you might start higher:
# workers = multiprocessing.cpu_count() * 2

# Worker class
# worker_class = "sync"
worker_class = "gevent" # Requires 'pip install gevent'

# Bind address and port
bind = "0.0.0.0:8000"

# Maximum number of requests a worker can handle before restarting
max_requests = 1000
# Set to 0 to disable
# max_requests = 0

# Timeout for worker requests
timeout = 30 # seconds

# Threads for gthread worker class (if used)
# threads = 2

# Logging configuration
loglevel = "info"
accesslog = "-" # Log to stdout
errorlog = "-"  # Log to stderr

When using Gunicorn behind Nginx, ensure Gunicorn is bound to a non-public interface (e.g., 127.0.0.1:8000 or unix:/path/to/socket.sock) and Nginx is configured to proxy requests to it.

PHP-FPM Tuning for PHP Applications

PHP-FPM (FastCGI Process Manager) is the standard way to run PHP applications efficiently. It manages a pool of PHP worker processes. Tuning PHP-FPM involves configuring the process manager and the PHP settings themselves.

Process Manager Settings (php-fpm.conf / pool.d/*.conf)

PHP-FPM configuration files are typically found in /etc/php/[version]/fpm/php-fpm.conf and pool configurations in /etc/php/[version]/fpm/pool.d/www.conf (or similar). The key directives for performance are within the pool configuration.

Process Management Modes

PHP-FPM offers three primary process management modes:

  • Static: A fixed number of child processes are created at startup. This offers the most predictable performance but can be less responsive to traffic fluctuations.
  • Dynamic: Starts with a minimum number of processes and spawns more up to a maximum as needed. Processes are then killed if they are idle for a certain period.
  • On-Demand: Starts only one process and spawns more as requests come in. Processes are killed when idle. This is the most memory-efficient but can have higher latency for initial requests.

For most production environments, Dynamic is a good balance. Static can be better if you have consistent, high traffic and want minimal overhead.

Tuning Example (pool.d/www.conf)

; /etc/php/[version]/fpm/pool.d/www.conf

; Choose one of the process management modes
; pm = static
pm = dynamic
; pm = ondemand

; For pm = static:
; pm.max_children = 50 ; Number of child processes to always maintain.

; For pm = dynamic:
pm.max_children = 35      ; Maximum number of children that can be started.
pm.min_spare_servers = 10 ; Minimum number of servers that should be kept idle.
pm.max_spare_servers = 20 ; Maximum number of servers that should be kept idle.
pm.max_requests = 500     ; Maximum number of requests each child process should serve before respawning.

; For pm = ondemand:
; pm.max_children = 50
; pm.max_requests = 500

; Process idle timeout (for dynamic and ondemand)
; pm.process_idle_timeout = 10s

; Listen socket
; listen = /run/php/php[version]-fpm.sock
listen = 127.0.0.1:9000 ; Or a TCP socket if preferred
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Other important settings
request_terminate_timeout = 60s ; Timeout for script execution
; request_slowlog_timeout = 10s ; Log scripts taking longer than this
; slowlog = /var/log/php/[version]/php-fpm-slow.log

The optimal values for pm.max_children, pm.min_spare_servers, and pm.max_spare_servers depend heavily on your server’s RAM and the nature of your PHP application. A common starting point for pm.max_children is (Total RAM - RAM used by OS/other services) / Average RAM per PHP process. Monitor memory usage closely.

PHP Configuration (php.ini)

Beyond PHP-FPM settings, core PHP directives in php.ini also impact performance. These are typically found in /etc/php/[version]/fpm/php.ini.

Key php.ini Directives

; In php.ini for FPM

; Memory limit for scripts
memory_limit = 256M ; Adjust as needed, avoid excessively high values

; Maximum execution time for scripts
max_execution_time = 60 ; Match or be less than FPM's request_terminate_timeout

; Maximum input variables
max_input_vars = 3000 ; Useful for complex forms

; OPcache settings (crucial for performance)
opcache.enable=1
opcache.memory_consumption=128 ; MB, adjust based on code size
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60 ; Check for file updates every 60 seconds
opcache.validate_timestamps=1 ; Set to 0 in production for max performance if deployments are managed carefully
opcache.enable_cli=0 ; Usually not needed for FPM

OPcache is non-negotiable for PHP performance. Ensure it’s enabled and properly configured. Setting opcache.validate_timestamps=0 in production can significantly boost performance by eliminating file stat checks on every request, but requires a manual cache clear or application restart after deployments.

MySQL/MariaDB Tuning on AWS

Database performance is often the bottleneck. Tuning MySQL/MariaDB involves configuring its buffer pools, query cache (though often deprecated/removed in newer versions), connection handling, and logging.

Key Configuration Variables (my.cnf / my.ini)

MySQL configuration is typically found in /etc/mysql/my.cnf, /etc/mysql/mysql.conf.d/mysqld.cnf, or similar paths. On AWS, you might be using RDS, which offers parameter groups for tuning.

InnoDB Buffer Pool Size

This is arguably the most critical setting for InnoDB performance. It caches table data and indexes in memory. A common recommendation is to set it to 50-75% of available RAM on a dedicated database server.

[mysqld]
innodb_buffer_pool_size = 4G ; Example for a server with 8GB RAM
innodb_buffer_pool_instances = 4 ; Typically set to number of CPU cores, or 1 per GB of buffer pool size

On AWS RDS, this is controlled by the innodb_buffer_pool_size parameter.

Connection Handling

Managing database connections efficiently is vital.

[mysqld]
max_connections = 200 ; Adjust based on application needs and server capacity
thread_cache_size = 16  ; Cache threads for reuse
wait_timeout = 600      ; Close idle connections after 10 minutes (default 8 hours)
interactive_timeout = 600 ; Close idle interactive connections

wait_timeout is important to prevent too many sleeping connections from consuming resources.

Query Cache (Deprecated/Removed)

The query cache was often a source of contention and is disabled by default in MySQL 5.7 and removed in MySQL 8.0. If you are on an older version and considering it, benchmark carefully. For modern deployments, focus on other optimizations.

Log Files

Slow query logging is essential for identifying performance bottlenecks.

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2 ; Log queries taking longer than 2 seconds
log_queries_not_using_indexes = 1 ; Log queries that don't use indexes

Regularly analyze the mysql-slow.log file using tools like pt-query-digest to optimize problematic queries.

Temporary Tables and Sort Buffers

[mysqld]
tmp_table_size = 64M
max_heap_table_size = 64M
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 1M
read_rnd_buffer_size = 2M

These buffers are allocated per connection/thread, so set them conservatively. Larger values can lead to excessive memory consumption if max_connections is high.

AWS Specific Considerations

When deploying on AWS, several factors come into play:

Instance Sizing

Choose instance types that match your workload. For compute-intensive tasks (like PHP processing), CPU-optimized instances (C-series) might be suitable. For memory-intensive databases, memory-optimized instances (R-series) are preferred. Ensure sufficient network bandwidth.

EBS Volumes

For database instances (RDS or EC2-hosted), use provisioned IOPS (io1/io2) or General Purpose SSD (gp3) volumes. gp3 offers better baseline performance and cost-effectiveness than gp2, allowing independent tuning of IOPS and throughput. Avoid magnetic volumes for production databases.

RDS Parameter Groups

For RDS, create custom parameter groups to modify MySQL/MariaDB settings. Changes made here are applied dynamically or upon instance reboot, depending on the parameter.

Database Read Replicas

Offload read traffic from your primary database instance by setting up read replicas. Configure your application to direct read-heavy queries to these replicas.

Caching Layers

Implement caching at various levels: Nginx (e.g., proxy_cache), application-level caching (e.g., Redis, Memcached), and database query caching (if applicable and carefully managed).

Monitoring and Iteration

Tuning is an iterative process. Continuous monitoring is key:

  • Nginx: Use stub_status module for connection metrics, access.log analysis, and system metrics (CPU, memory, network).
  • Gunicorn/PHP-FPM: Monitor worker status, request latency, error rates, and system resource usage. PHP-FPM has its own status page.
  • MySQL: Monitor SHOW GLOBAL STATUS, SHOW ENGINE INNODB STATUS, slow query logs, and system resource usage (CPU, I/O, memory). AWS CloudWatch provides excellent metrics for RDS.
  • Application Performance Monitoring (APM): Tools like Datadog, New Relic, or Sentry provide deep insights into application-level performance, database query times, and external service calls.

Start with conservative settings, monitor the impact, and gradually adjust parameters based on observed performance 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

  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8’s JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends
  • Beyond the Basics: Leveraging PHP 8.3’s JIT Compiler and Fibers for High-Concurrency Laravel Applications
  • Zero-Downtime Deployments with Docker, Laravel, and AWS ECS: A Deep Dive into Blue/Green Strategies

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (15)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (19)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (25)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8's JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

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