• 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 MongoDB on AWS for Python

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MongoDB 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. We’ll focus on key directives for a production environment.

Worker Processes and Connections

The number of worker processes should generally match the number of CPU cores available on the server. This allows Nginx to effectively utilize all available processing power without excessive context switching. The worker_connections directive defines the maximum number of simultaneous connections that each worker process can handle. A common starting point is 1024, but this can be tuned based on application needs and system limits.

Tuning worker_processes and worker_connections

Determine the number of CPU cores using nproc or by inspecting /proc/cpuinfo. Then, set these directives in your main nginx.conf file, typically within the events block.

Example nginx.conf snippet

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

http {
    # ... other http configurations ...

    server {
        listen 80;
        server_name your_domain.com;

        # ... server configurations ...
    }
}

Buffering and Keepalive

Nginx’s buffering directives control how it handles request and response bodies. Properly configured buffering can reduce the load on your backend application servers by allowing Nginx to handle slow clients or large data transfers. client_body_buffer_size, client_header_buffer_size, and large_client_header_buffers are important. The keepalive_timeout directive controls how long an idle keep-alive connection will remain open, balancing resource utilization with the ability to reuse connections.

Buffering Configuration

http {
    # ...

    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 128k; # Number of buffers and their size

    send_timeout 60s; # Timeout for sending data to client
    client_timeout 60s; # Timeout for reading data from client

    keepalive_timeout 65s; # Keep-alive timeout
    keepalive_requests 1000; # Max requests per keep-alive connection

    # ...
}

Gzip Compression

Enabling Gzip compression significantly reduces the amount of data transferred over the network, leading to faster page load times and lower bandwidth consumption. It’s essential to configure it correctly to avoid compressing already compressed content (like images) or overwhelming the CPU.

Gzip Directives

http {
    # ...

    gzip on;
    gzip_vary on; # Adds 'Vary: Accept-Encoding' header
    gzip_proxied any; # Compress responses for proxied requests
    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;
    gzip_min_length 1000; # Minimum response length to compress
    gzip_disable "msie6"; # Disable for older IE versions if necessary

    # ...
}

Static File Serving and Caching

Nginx excels at serving static assets. Configure appropriate cache headers to leverage browser caching and reduce the load on your backend. Use expires or Cache-Control directives.

Static File Caching Configuration

location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 365d; # Cache for 1 year
    add_header Cache-Control "public, no-transform";
    access_log off; # Optionally disable access logs for static files
}

Gunicorn: The Python WSGI HTTP Server

Gunicorn (Green Unicorn) is a popular WSGI HTTP Server for Python. It’s known for its simplicity, robustness, and performance. Tuning Gunicorn involves managing worker processes, threads, and timeouts to match your application’s characteristics and server resources.

Worker Types and Scaling

Gunicorn supports several worker types: sync (synchronous, default), eventlet, gevent, and tornado. For most CPU-bound Python applications, the sync worker type is sufficient and straightforward. For I/O-bound applications that benefit from asynchronous handling, eventlet or gevent can offer better concurrency. The number of workers is a critical tuning parameter. A common recommendation is (2 * number_of_cores) + 1, but this should be adjusted based on memory usage and application behavior.

Gunicorn Command-Line Configuration

Here’s an example of how to start Gunicorn with optimized settings. This assumes you have a WSGI application object named application in a file named wsgi.py.

Example Gunicorn Startup Command

For a server with 4 CPU cores:

Example Gunicorn Command

gunicorn --workers 9 \
         --worker-class sync \
         --bind 0.0.0.0:8000 \
         --timeout 120 \
         --graceful-timeout 120 \
         --threads 2 \
         wsgi:application

Explanation:

  • --workers 9: For 4 cores, (2 * 4) + 1 = 9 workers. Adjust based on memory.
  • --worker-class sync: Standard synchronous worker.
  • --bind 0.0.0.0:8000: Listen on all interfaces, port 8000. Nginx will proxy to this.
  • --timeout 120: Maximum time a worker can spend processing a request. Crucial for preventing worker hangs.
  • --graceful-timeout 120: Time to wait for workers to shut down gracefully during restarts.
  • --threads 2: For sync workers, this enables threading within each worker process. This can improve concurrency for I/O-bound tasks within a single worker.

Worker Class Considerations

If your application is heavily I/O bound (e.g., making many external API calls, database queries that don’t block the event loop), consider using gevent or eventlet. These worker classes use green threads (coroutines) to handle many concurrent connections with fewer OS threads.

Example with gevent

pip install gunicorn gevent
gunicorn --workers 4 \
         --worker-class gevent \
         --bind 0.0.0.0:8000 \
         --timeout 120 \
         --graceful-timeout 120 \
         --worker-connections 1000 \
         wsgi:application

Note: With gevent, you typically use fewer worker processes (often matching CPU cores) and rely on --worker-connections to handle concurrency.

Logging Configuration

Effective logging is vital for debugging and monitoring. Gunicorn can log to stdout/stderr (ideal for containerized environments) or to files.

Example Logging Setup

gunicorn --workers 9 \
         --worker-class sync \
         --bind 0.0.0.0:8000 \
         --timeout 120 \
         --access-logfile /var/log/gunicorn/access.log \
         --error-logfile /var/log/gunicorn/error.log \
         --log-level info \
         wsgi:application

PHP-FPM: For PHP Applications

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

Process Manager Settings

PHP-FPM offers three primary process management strategies: static, dynamic, and ondemand. Each has trade-offs regarding resource utilization and responsiveness.

Process Manager Configuration (php-fpm.conf or pool config)

The main configuration file is typically /etc/php/[version]/fpm/php-fpm.conf, and pool-specific settings are in /etc/php/[version]/fpm/pool.d/www.conf (or a custom pool name).

Example Pool Configuration (www.conf)

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

; Process Manager Settings
; pm = dynamic # Options: static, dynamic, ondemand
; pm.max_children = 50
; pm.start_servers = 5
; pm.min_spare_servers = 2
; pm.max_spare_servers = 8
; pm.max_requests = 500

; For static process management (fixed number of workers)
pm = static
pm.max_children = 100 ; Adjust based on available RAM and CPU

; For dynamic process management (adjusts based on load)
; pm = dynamic
; pm.max_children = 100
; pm.start_servers = 10
; pm.min_spare_servers = 5
; pm.max_spare_servers = 15
; pm.max_requests = 1000 ; Restart worker after this many requests

; For ondemand process management (spawns workers as needed)
; pm = ondemand
; pm.max_children = 100
; pm.process_idle_timeout = 10s
; pm.max_requests = 1000

; Other important settings
request_terminate_timeout = 120s
request_slowlog_timeout = 10s
slowlog = /var/log/php/php-fpm-slow.log
catch_workers_output = yes

Tuning Strategy:

  • static: Best for predictable, high-traffic loads where you can precisely allocate resources. Requires careful calculation of pm.max_children to avoid OOM errors.
  • dynamic: A good balance for variable loads. PHP-FPM manages worker count between min_spare_servers and max_spare_servers, scaling up to max_children.
  • ondemand: Saves resources when idle but can introduce latency on initial requests as workers are spawned. Suitable for low-traffic or bursty workloads.

Calculating pm.max_children: This is the most critical setting. A common formula is: pm.max_children = (Total RAM - RAM for OS/Nginx/Other) / Average RAM per PHP-FPM worker. Monitor memory usage closely.

Nginx Configuration for PHP-FPM

Nginx communicates with PHP-FPM via FastCGI. Ensure your Nginx configuration correctly passes requests to the PHP-FPM socket or TCP port.

Example Nginx Location Block for PHP

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    # With php-fpm (or other unix sockets):
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    # With php-fpm (or other tcp sockets):
    # fastcgi_pass 127.0.0.1:9000;
}

MongoDB Performance Tuning on AWS

Optimizing MongoDB, especially on cloud platforms like AWS, involves careful consideration of instance types, storage, network, and database configuration. For production, consider using Amazon DocumentDB (compatible with MongoDB APIs) or self-managed MongoDB on EC2 instances.

Instance Selection (EC2)

Choose instance types that balance CPU, RAM, and Network I/O. For database workloads:

  • Memory-Optimized Instances (r-series): Ideal if your working set fits largely in RAM.
  • Compute-Optimized Instances (c-series): Good for CPU-intensive operations.
  • Storage-Optimized Instances (i-series, d-series): Crucial if your working set exceeds available RAM and you rely heavily on disk I/O. NVMe SSDs on these instances offer very low latency.

Storage Configuration (EBS)

When using EBS volumes with EC2 instances:

  • gp3 (General Purpose SSD): Offers baseline performance and allows independent provisioning of IOPS and throughput. Often the best cost-performance choice.
  • io1/io2 (Provisioned IOPS SSD): For workloads requiring consistent, high IOPS. More expensive but guarantees performance.
  • Throughput Optimization: Ensure your EBS volume’s throughput is sufficient for your workload. MongoDB can be I/O bound.
  • RAID Configuration: For higher performance and redundancy, consider RAID 0 (for performance) or RAID 10 (for performance and redundancy) across multiple EBS volumes, especially if not using instance store volumes.

MongoDB Configuration File (mongod.conf)

Key parameters in /etc/mongod.conf (or equivalent) for tuning:

Example mongod.conf Snippet

storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
    # commitInterval: 100ms # Adjust for write-heavy workloads, default is 300ms
  engine: wiredTiger
  wiredTiger:
    collectionConfig:
      cacheSizeGB: 0.75 # Default is 50% of RAM for WiredTiger, adjust if needed
    # For WiredTiger, cache is managed automatically.
    # The 'cacheSizeGB' is for the collection data.
    # The WiredTiger internal cache is separate and usually sufficient.
    # If you need to tune it, it's a more advanced topic.

# network:
#   bindIp: 127.0.0.1 # Or specific IPs for replication/sharding

operationProfiling:
  mode: slowOp # Log slow operations
  slowOpThresholdMs: 100 # Log operations taking longer than 100ms

# sharding: # If using sharding
#   clusterRole: configsvr # or shardsvr

# replication: # If using replica sets
#   replSetName: rs0

WiredTiger Cache: WiredTiger’s default is to use 50% of system RAM for its cache. This is often a good starting point. If your working set is significantly larger than RAM, you might need to increase this, but be cautious not to starve the OS or other processes. Monitor cache hit rates.

Indexing Strategy

Proper indexing is paramount. Use explain() on your queries to identify missing or inefficient indexes. Regularly review index usage and remove unused indexes.

Identifying Slow Queries

Enable the slow query log in mongod.conf (as shown above) and analyze the logs. Tools like mongotop and mongostat provide real-time performance metrics.

Example Index Creation

db.collection.createIndex( { field1: 1, field2: -1 } )

Connection Pooling

Ensure your application’s MongoDB driver is configured with an appropriate connection pool size. Too few connections can lead to request queuing; too many can exhaust server resources.

Example Python (PyMongo) Connection Pooling

from pymongo import MongoClient

# Default pool size is 20. Adjust as needed.
client = MongoClient('mongodb://localhost:27017/', maxPoolSize=100)

Monitoring and Alerting

Implement robust monitoring for all components. Key metrics include:

  • Nginx: Request rate, error rate (4xx, 5xx), connection count, latency.
  • Gunicorn/PHP-FPM: Worker status (idle, active, busy), request processing time, error counts.
  • MongoDB: Query performance, cache hit rate, disk I/O, network traffic, replication lag, connection count, memory usage.

Utilize AWS CloudWatch, Prometheus/Grafana, Datadog, or similar tools for comprehensive visibility and alerting.

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

  • 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
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala