• 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 Python

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

Nginx as a High-Performance Frontend Proxy

For Python web applications, Nginx serves as an indispensable frontend proxy, efficiently handling static file serving, SSL termination, request buffering, and load balancing. Optimizing Nginx is crucial 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 adjust include:

  • worker_processes: Set this to the number of CPU cores available. For optimal performance, it’s often recommended to set it to the number of physical cores, not logical cores.
  • worker_connections: This defines the maximum number of simultaneous connections a worker process can handle. A common starting point is 1024, but this can be increased significantly based on system resources and expected load. Ensure your system’s file descriptor limit (ulimit -n) is high enough to accommodate this.
  • multi_accept: Set to on to allow worker processes to accept multiple connections at once.
  • keepalive_timeout: Controls the timeout for persistent HTTP connections. A value between 60 and 120 seconds is often a good balance between resource utilization and client responsiveness.
  • 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 both to on for improved network performance, especially with HTTP/1.1.

A sample global configuration snippet:

worker_processes auto; # Or set to the number of physical CPU cores
events {
    worker_connections 4096; # Adjust based on system limits and expected load
    multi_accept on;
}

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; # Optional: limit requests per keepalive connection

    # Gzip compression for static assets and API responses
    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;

    # Buffering settings
    client_body_buffer_size    128k;
    client_max_body_size       10m; # Adjust as needed for file uploads
    client_header_buffer_size  16k;
    large_client_header_buffers  4 128k;

    # Proxy buffering
    proxy_buffering on;
    proxy_buffer_size 128k;
    proxy_buffers 8 128k;
    proxy_busy_buffers_size 256k;

    # Other optimizations
    open_file_cache max=2000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Include server blocks
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Server Block Configuration for Python Applications

Each Python application will typically have its own server block. This block defines how Nginx interacts with your application server (Gunicorn or uWSGI) and handles static assets.

Key directives within a server block:

  • listen: The port Nginx listens on (e.g., 80, 443).
  • server_name: The domain name(s) for this server block.
  • location /: This block handles requests for the root path. It’s crucial for proxying requests to your application server.
  • location /static/: This block is dedicated to serving static files directly from the filesystem, bypassing the Python application entirely for performance gains.
  • proxy_pass: Specifies the upstream server (e.g., Gunicorn’s socket or HTTP endpoint).
  • proxy_set_header: Essential for passing correct client information to the backend application (e.g., Host, X-Real-IP, X-Forwarded-For).
  • proxy_read_timeout, proxy_connect_timeout, proxy_send_timeout: Adjust these to prevent premature timeouts, especially for long-running requests.

Example server block for a Gunicorn-backed application:

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;

    # Serve static files directly
    location /static/ {
        alias /path/to/your/app/static/;
        expires 30d; # Cache static assets for 30 days
        access_log off;
        add_header Cache-Control "public";
    }

    # Proxy requests to Gunicorn
    location / {
        proxy_pass http://unix:/path/to/your/app/app.sock; # Or http://127.0.0.1:8000;
        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_read_timeout 300s; # Increase for long-running tasks
        proxy_send_timeout 300s;

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

    # Optional: Handle specific API endpoints with different timeouts
    # location /api/ {
    #     proxy_pass http://unix:/path/to/your/app/app.sock;
    #     # ... other proxy settings ...
    #     proxy_read_timeout 600s; # Longer timeout for API
    # }

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

AWS Specific Considerations

On AWS, Nginx is often deployed on EC2 instances or within ECS/EKS. Ensure your Security Groups allow inbound traffic on port 80/443. For SSL termination, you’ll typically use AWS Certificate Manager (ACM) and configure Nginx to listen on port 443 with the appropriate SSL certificates. Consider using AWS WAF for additional security.

Gunicorn: The Python WSGI HTTP Server

Gunicorn (Green Unicorn) is a popular WSGI HTTP Server for Python. Its performance is heavily influenced by the number of worker processes, worker type, and communication method with Nginx.

Worker Configuration

The most critical Gunicorn setting is the number of worker processes. The recommended formula is (2 * Number of CPU Cores) + 1. This aims to keep CPU cores busy while accounting for I/O wait times.

Worker types also matter:

  • sync: The default and simplest worker type. Each worker handles one request at a time. Suitable for CPU-bound tasks or when I/O is not a bottleneck.
  • gevent: Uses greenlets for asynchronous I/O. Excellent for I/O-bound applications (e.g., making many external API calls). Requires installing the gevent library.
  • eventlet: Similar to gevent, also uses greenlets for asynchronous I/O.

For most I/O-bound web applications, gevent or eventlet will yield better concurrency and throughput than sync workers.

Gunicorn Command-Line Arguments and Configuration File

You can configure Gunicorn via command-line arguments or a Python configuration file (e.g., gunicorn_config.py).

Example command-line invocation:

gunicorn --workers 5 --worker-class gevent --bind unix:/path/to/your/app/app.sock --timeout 120 your_app.wsgi:application

Explanation:

  • --workers 5: Set to (2 * CPU Cores) + 1. If you have 2 CPU cores, this would be 5.
  • --worker-class gevent: Using the gevent worker class for asynchronous I/O.
  • --bind unix:/path/to/your/app/app.sock: Binding to a Unix socket is generally faster and more secure than binding to a TCP port when Nginx and Gunicorn are on the same host.
  • --timeout 120: Sets the worker timeout to 120 seconds. Adjust this based on your application’s longest expected request.
  • your_app.wsgi:application: Points to your WSGI application object.

Example gunicorn_config.py:

import multiprocessing

# Number of worker processes
workers = multiprocessing.cpu_count() * 2 + 1

# Worker class (e.g., gevent, eventlet, sync)
worker_class = "gevent" # Or "sync" for simpler applications

# Bind to a Unix socket
bind = "unix:/path/to/your/app/app.sock"

# Worker timeout (seconds)
timeout = 120

# Maximum number of requests a worker can process before restarting
max_requests = 5000
max_requests_jitter = 1000 # Helps distribute worker restarts

# Logging configuration
loglevel = "info"
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"

# Other useful settings
# daemon = True # Run as a daemon (often managed by systemd/supervisor)
# pidfile = "/var/run/gunicorn.pid"
# user = "www-data"
# group = "www-data"
# chdir = "/path/to/your/app" # Change directory to your app's root

AWS Specific Considerations

When running Gunicorn on EC2, ensure the user running Gunicorn has write permissions to the directory where the Unix socket is created. If using ECS/EKS, Gunicorn is typically run as a container. The bind address should be accessible by Nginx (e.g., within the same network namespace or via a service discovery mechanism). Monitor Gunicorn logs for errors and performance bottlenecks. Consider using systemd or supervisor to manage Gunicorn processes for reliability.

PHP-FPM: For PHP Applications

If your application stack includes PHP, PHP-FPM (FastCGI Process Manager) is the standard way to interface PHP with web servers like Nginx. Tuning PHP-FPM is critical for handling concurrent PHP requests efficiently.

PHP-FPM Pool Configuration

PHP-FPM pools are defined in configuration files, typically located in /etc/php/[version]/fpm/pool.d/www.conf. The key settings revolve around process management and resource allocation.

Process Management Settings

These directives control how PHP-FPM manages its worker processes:

  • pm: Process manager control. Options:
    • static: A fixed number of child processes.
    • dynamic: Starts with a minimum number of processes and spawns more up to a maximum as needed.
    • ondemand: Starts no children initially; spawns them only when a request arrives.
  • pm.max_children: The maximum number of child processes that will be spawned. This is the most critical setting for preventing OOM errors. Set this based on available RAM.
  • 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 to maintain.
  • pm.max_spare_servers: The maximum number of idle (spare) processes to maintain.
  • pm.max_requests: The number of requests each child process will execute before respawning. Setting this helps prevent memory leaks from accumulating over time.

For a busy server, dynamic is often a good choice. For predictable loads, static can offer slightly better performance by avoiding process spawning overhead.

Example www.conf snippet (dynamic process management):

; Start a pool named 'www'
[www]

; Choose the process manager: 'static', 'dynamic' or 'ondemand'
pm = dynamic

; For dynamic PM:
; The initial number of child processes to start.
pm.max_children = 150 ; Adjust based on available RAM. (Total RAM - OS - Nginx - Other Services) / Average PHP Process Size
; The default number of *idle* spawned processes.
pm.min_spare_servers = 20
; The maximum number of *idle* spawned processes.
pm.max_spare_servers = 50

; The number of requests each child process should execute before respawning.
; This is useful to prevent memory leaks from accumulating.
pm.max_requests = 1000

; The TCP socket or Unix socket on which to listen.
; For Nginx, a Unix socket is generally preferred for performance.
listen = /run/php/php7.4-fpm.sock ; Adjust path and version as needed

; Set permissions for the socket
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Other useful settings
request_terminate_timeout = 120s ; Timeout for script execution
; rlimit_files = 4096 ; Max open files per process
; rlimit_nofile = 4096 ; Max open files per process (same as above, often)
; catch_workers_output = yes ; Log worker output to stderr/stdout
; slowlog = /var/log/php/php-fpm-slow.log ; Log slow requests

Nginx Configuration for PHP-FPM

Nginx needs to be configured to pass PHP requests to the PHP-FPM socket.

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

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Make sure this path matches your PHP-FPM pool's listen directive
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny all;
    }
}

AWS Specific Considerations

When deploying PHP-FPM on AWS, ensure the PHP-FPM service is running and accessible by Nginx. If Nginx and PHP-FPM are on different EC2 instances, you’d configure PHP-FPM to listen on a TCP port (e.g., listen = 127.0.0.1:9000) and update Nginx’s fastcgi_pass directive accordingly. For containerized deployments (ECS/EKS), PHP-FPM runs within its own container, and Nginx communicates with it via the defined network. Monitor PHP-FPM logs for errors and resource usage. Adjust pm.max_children carefully to avoid exhausting EC2 instance memory.

MySQL/MariaDB Performance Tuning on AWS RDS/EC2

Database performance is often the ultimate bottleneck. Tuning MySQL/MariaDB, especially when hosted on AWS RDS or EC2, requires a multi-faceted approach.

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

The my.cnf (or my.ini) file contains crucial tuning parameters. For AWS RDS, many of these are controlled via Parameter Groups.

  • innodb_buffer_pool_size: The most important setting for InnoDB. It’s the memory area that InnoDB uses to cache table data and indexes. Aim for 50-80% of your instance’s available RAM on a dedicated database server. For RDS, this is often set automatically based on instance type, but can be adjusted.
  • innodb_log_file_size: Controls the size of the redo log files. Larger logs can improve write performance but increase recovery time. A common recommendation is 256M to 1G or more, depending on write volume. Ensure innodb_log_files_in_group is also set (e.g., 2).
  • innodb_flush_log_at_trx_commit: Controls durability vs. performance.
    • 1 (default): Fully ACID compliant. Logs are flushed to disk on every commit. Safest but slowest.
    • 0: Logs are written to the log buffer and flushed to disk roughly once per second. Faster, but you might lose up to 1 second of transactions in a crash.
    • 2: Logs are written on commit, but flushed to disk by the OS roughly once per second. Faster than 1, safer than 0.
    For many web applications, setting this to 2 offers a good balance.
  • max_connections: The maximum number of simultaneous client connections. Set this based on your application’s needs and server capacity. Too high can lead to resource exhaustion.
  • query_cache_size and query_cache_type: The query cache is deprecated in MySQL 5.7 and removed in 8.0. If using older versions, it can be beneficial for read-heavy workloads with identical queries, but it can also cause contention. Generally, disable it (0) for modern applications.
  • tmp_table_size and max_heap_table_size: Control the maximum size of in-memory temporary tables. Increasing these can help complex queries that require temporary tables.
  • sort_buffer_size, join_buffer_size, read_buffer_size, read_rnd_buffer_size: These are per-connection buffers. Increase cautiously, as they can consume significant memory if max_connections is high.

Example my.cnf snippet (for a dedicated DB server, adjust values based on instance size):

[mysqld]
# General Settings
user                    = mysql
pid-file                = /var/run/mysqld/mysqld.pid
socket                  = /var/run/mysqld/mysqld.sock
port                    = 3306
basedir                 = /usr
datadir                 = /var/lib/mysql
tmpdir                  = /tmp
# log_error               = /var/log/mysql/error.log # Managed by RDS

# InnoDB Settings
innodb_buffer_pool_size = 4G ; Example: 4GB for an instance with 8GB RAM
innodb_log_file_size    = 512M
innodb_log_files_in_group = 2
innodb_flush_log_at_trx_commit = 2 ; Balance between performance and durability
innodb_flush_method     = O_DIRECT ; Recommended for Linux/XFS/ext4
innodb_file_per_table   = 1 ; Recommended for better management

# Connection Settings
max_connections         = 200 ; Adjust based on application needs and instance capacity
# thread_cache_size       = 16 ; Cache threads for reuse

# Temporary Tables
tmp_table_size          = 64M
max_heap_table_size     = 64M

# Per-connection Buffers (Increase cautiously)
# sort_buffer_size        = 2M
# join_buffer_size        = 2M
# read_buffer_size        = 1M
# read_rnd_buffer_size    = 2M

# Query Cache (Deprecated in MySQL 5.7, removed in 8.0)
# query_cache_type        = 0
# query_cache_size        = 0

# Other
# table_open_cache        = 2000
# table_definition_cache  = 1000
# explicit_defaults_for_timestamp = 1

AWS RDS Specific Tuning

For RDS, you’ll primarily use **Parameter Groups**. Create a custom parameter group based on the default for your engine version. Then, modify parameters like:

  • innodb_buffer_pool_size: Set to 50-75% of instance memory.
  • innodb_log_file_size: Adjust as needed.
  • innodb_flush_log_at_trx_commit: Set to 2 for better performance if acceptable.
  • max_connections: Tune based on application load.
  • slow_query_log and long_query_time: Enable the slow query log (e.g., long_query_time = 2) to identify problematic queries.

After modifying a parameter group, you must **apply** it to your RDS instance and typically **reboot** the instance for changes to take effect (especially for buffer pool size and log file sizes).

Query Optimization and Indexing

No amount of server tuning can fix poorly written queries. Regularly analyze slow queries using:

  • The slow query log (enabled via parameter groups).
  • EXPLAIN statements before your queries to understand their execution plan.
  • Tools like Percona Toolkit’s pt-query-digest to analyze slow logs.

Ensure appropriate indexes are in place for your WHERE clauses, JOIN conditions, and ORDER BY clauses. Avoid indexing every column; focus on columns used in filtering and joining.

AWS EC2 Specific Tuning

If running MySQL/MariaDB directly on EC2, you’ll edit the my.cnf file directly (usually in /etc/mysql/my.cnf or /etc/my.cnf). Ensure the data directory (datadir) is on a performant EBS volume (e.g., `gp3` or `io1`/`io2` if high IOPS are needed). Monitor system resources (CPU, RAM, Disk I/O) using CloudWatch metrics.

Putting It All Together: A Holistic Approach

Optimizing a web stack is an iterative process. Start with sensible defaults, monitor performance under load, identify bottlenecks, and tune accordingly. Use tools like:

  • Nginx: nginx -t for configuration testing, access.log, error.log, ngx_http_stub_status_module.
  • Gunicorn: Logs, htop/top for process monitoring.
  • PHP-FPM: slowlog, access.log, error.log, pm.status_path.
  • MySQL: Slow query log, SHOW GLOBAL STATUS;, SHOW ENGINE INNODB STATUS;, EXPLAIN, CloudWatch metrics (for RDS/EC2).
  • Application Profiling: Use tools like cProfile (Python), Xdebug (PHP), or APM solutions (New Relic, Datadog, Sentry) to pinpoint application-level performance issues.

Remember that AWS instance types offer different CPU, RAM, and network performance characteristics. Choose instance types that align with your application’s needs. For databases, consider RDS instance types optimized for storage or I/O. Regularly review and adjust your configurations as your application’s load and usage patterns evolve.

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