• 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 AWS for Laravel

The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and PostgreSQL on AWS for Laravel

Nginx Tuning for Laravel Applications

Optimizing Nginx is crucial for serving Laravel applications efficiently, especially under load. We’ll focus on key directives that impact performance, concurrency, and resource utilization on AWS.

Worker Processes and Connections

The worker_processes directive controls 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. A common starting point is 1024, but this should be tuned based on your application’s concurrency needs and server’s memory.

Keepalive Connections

Enabling HTTP keep-alive connections reduces the overhead of establishing new TCP connections for each request. The keepalive_timeout directive specifies how long an idle keep-alive connection will remain open. A value between 60 and 120 seconds is often a good balance. keepalive_requests limits the number of requests that can be served over a single keep-alive connection; setting it to a high value like 1000 can further reduce overhead.

Buffering and Caching

Nginx buffering can significantly improve performance by allowing it to handle requests and responses more efficiently. client_body_buffer_size and client_header_buffer_size control the buffer sizes for client request bodies and headers, respectively. For large file uploads, you might need to increase client_body_buffer_size. proxy_buffering should generally be on when proxying to your application server (Gunicorn/FPM) to allow Nginx to buffer responses from the backend, improving client throughput. proxy_buffer_size and proxy_buffers control the size and number of these buffers.

Gzip Compression

Enabling Gzip compression can drastically reduce the amount of data transferred over the network, leading to faster page loads. Ensure you configure gzip_types to include common MIME types served by your Laravel application, such as text/html, text/css, application/javascript, and application/json. gzip_comp_level controls the compression level (1-9), with 6 being a good default for balancing compression ratio and CPU usage.

Example Nginx Configuration Snippet

Here’s a consolidated example for a typical Laravel setup:

worker_processes auto;
events {
    worker_connections 4096; # Adjust based on server resources and expected load
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    keepalive_timeout 65;
    keepalive_requests 1000;

    # Buffering settings
    client_body_buffer_size    10m; # For larger file uploads, adjust as needed
    client_header_buffer_size  1k;
    large_client_header_buffers 4 32k;

    proxy_buffering on;
    proxy_buffer_size 128k;
    proxy_buffers 8 128k;
    proxy_busy_buffers_size 256k;

    # Gzip Compression
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # SSL Configuration (if applicable)
    # ssl_protocols TLSv1.2 TLSv1.3;
    # ssl_prefer_server_ciphers on;
    # ssl_session_cache shared:SSL:10m;
    # ssl_session_timeout 10m;

    server {
        listen 80;
        # listen 443 ssl http2; # Uncomment for SSL

        server_name your_domain.com;
        root /var/www/your_laravel_app/public;
        index index.php index.html index.htm;

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

        location ~ \.php$ {
            include snippets/fastcgi_params.conf;
            # Adjust fastcgi_pass based on your PHP-FPM configuration
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Example for PHP 8.1
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_read_timeout 300; # Increase for long-running PHP scripts
        }

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

        # Caching for static assets (adjust cache duration as needed)
        location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 1y;
            add_header Cache-Control "public, no-transform";
            access_log off;
        }
    }
}

Gunicorn/PHP-FPM Tuning for Laravel

The application server (Gunicorn for Python/Flask/Django, or PHP-FPM for PHP/Laravel) is where your application code actually runs. Tuning these is critical for handling requests efficiently and preventing bottlenecks.

Gunicorn Tuning (Python)

Gunicorn is a popular WSGI HTTP Server for Python. Key tuning parameters include the number of worker processes and the worker type.

Worker Processes

The number of worker processes directly impacts concurrency. A common recommendation is (2 * number_of_cores) + 1. This formula aims to keep CPU cores busy while accounting for I/O waits.

Worker Type

Gunicorn supports several worker types:

  • sync: The default and simplest worker type. It handles one request at a time per worker. Not ideal for I/O-bound applications.
  • eventlet, gevent: Asynchronous worker types that use green threads to handle many requests concurrently within a single process. These are generally preferred for I/O-bound applications like web services that make external API calls or database queries.

For most Laravel-like applications (even if running on Python frameworks), using gevent or eventlet workers is highly recommended for better concurrency and resource utilization.

Example Gunicorn Command Line

gunicorn --workers 3 --worker-class gevent --bind 0.0.0.0:8000 your_project.wsgi:application

In this example, --workers 3 is set for a 2-core CPU, and --worker-class gevent is used for asynchronous handling. Adjust --workers based on your CPU count and application’s I/O characteristics. The --bind address and port should match what Nginx is configured to proxy to.

PHP-FPM Tuning (PHP/Laravel)

PHP-FPM (FastCGI Process Manager) is the standard way to run PHP applications. Its configuration is primarily managed in php-fpm.conf and pool configuration files (e.g., www.conf).

Process Management Modes

PHP-FPM offers three process management modes:

  • static: A fixed number of child processes are spawned when the FPM master process starts. This offers the most predictable performance but can be inefficient if traffic fluctuates wildly.
  • dynamic: The number of child processes varies based on traffic. It starts with a minimum number and spawns more up to a maximum as needed. This is a good balance for most applications.
  • ondemand: Child processes are spawned only when a request is received. This saves memory but can introduce latency for the first request after a period of inactivity.

For most Laravel applications, the dynamic mode is a good starting point. You’ll tune parameters like pm.max_children, pm.start_servers, pm.min_spare_servers, and pm.max_spare_servers.

Tuning Parameters for Dynamic Mode

pm.max_children: The maximum number of child processes that can be active at the same time. This is the most critical setting and should be determined by your server’s available RAM. A common formula is (Total RAM - RAM used by OS/Nginx) / Average RAM per PHP process. Monitor memory usage closely.

pm.start_servers: The number of child processes to start when the FPM master process is started.

pm.min_spare_servers: The minimum number of idle supervisor processes. If there are fewer idle processes than this number, the master process will fork more child processes.

pm.max_spare_servers: The maximum number of idle supervisor processes. If there are more idle processes than this number, the master process will terminate some of them.

Example PHP-FPM Pool Configuration (www.conf)

[www]
user = www-data
group = www-data
listen = /var/run/php/php8.1-fpm.sock # Match this with Nginx's fastcgi_pass
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 100       ; Adjust based on server RAM (e.g., 100 * 50MB/process = 5GB)
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.process_idle_timeout = 10s
pm.max_requests = 500       ; Restart processes after X requests to prevent memory leaks

request_terminate_timeout = 120 ; Timeout for individual PHP requests (seconds)
request_slowlog_timeout = 30    ; Log requests slower than this (seconds)
slowlog = /var/log/php/php8.1-fpm.slow.log

catch_workers_output = yes
catch_workers_output_level = notice

Important: After modifying PHP-FPM configuration, you must restart the PHP-FPM service:

sudo systemctl restart php8.1-fpm

PostgreSQL Tuning for Laravel

Database performance is often the ultimate bottleneck. Tuning PostgreSQL, especially on AWS RDS or EC2, requires careful consideration of memory, I/O, and query optimization.

Key PostgreSQL Configuration Parameters

These parameters are typically set in postgresql.conf. On AWS RDS, you’ll manage these via Parameter Groups.

shared_buffers

This is the most important parameter. It defines the amount of memory dedicated to caching data blocks read from disk. A common recommendation is 25% of your total system RAM, but this can be higher (up to 40%) on dedicated database servers with ample RAM. For RDS, this is often set automatically based on instance size, but you can override it.

work_mem

This parameter controls the amount of memory that can be used for internal sort operations and hash tables before writing to temporary disk files. If your queries involve large sorts or joins, increasing work_mem can significantly speed them up. However, be cautious: this memory is allocated *per operation*, so a high value combined with many concurrent queries can exhaust RAM. Start with a moderate value (e.g., 16MB-64MB) and monitor query performance and memory usage.

maintenance_work_mem

This is the maximum memory to be used for maintenance operations like VACUUM, CREATE INDEX, and ALTER TABLE. A higher value can speed up these operations. A common recommendation is 10-25% of total RAM, but it doesn’t need to be as high as shared_buffers.

effective_cache_size

This parameter informs the query planner about how much memory is available for disk caching by the operating system and PostgreSQL’s shared buffers. It doesn’t allocate memory itself but influences the planner’s decisions. A good starting point is 50-75% of total RAM.

max_connections

The maximum number of concurrent connections allowed. This should be set based on your application’s needs and server capacity. Too high a value can lead to excessive memory consumption. Ensure your application connection pooler (if used) is configured appropriately.

wal_buffers

Memory for Write-Ahead Log (WAL) information. A value of -1 (auto) is often sufficient, but setting it to a reasonable value like 16MB can sometimes improve write performance.

random_page_cost and seq_page_cost

These parameters influence the query planner’s cost estimates for different types of disk I/O. On modern SSDs (common on AWS), random I/O is much faster than traditional HDDs. Lowering random_page_cost (e.g., from 4.0 to 1.1 or 1.5) can encourage the planner to use index scans more often, which can be beneficial for read-heavy workloads.

Example PostgreSQL Configuration Snippet (for a 32GB RAM instance)

# Memory Settings
shared_buffers = 8GB             # ~25% of 32GB RAM
work_mem = 64MB                  # Start here, monitor queries
maintenance_work_mem = 4GB       # ~12.5% of 32GB RAM
effective_cache_size = 24GB      # ~75% of 32GB RAM

# Connection Settings
max_connections = 200            # Adjust based on application needs and server capacity

# WAL Settings
wal_buffers = 16MB
wal_writer_delay = 200ms         # Default is 200ms, can be tuned
commit_delay = 10ms              # Can improve performance for high-concurrency writes
commit_siblings = 5              # Number of concurrent transactions to wait for before committing

# Planner Cost Settings (for SSDs)
random_page_cost = 1.5           # Lower for SSDs
seq_page_cost = 1.0              # Default

# Logging
log_min_duration_statement = 1000 # Log statements longer than 1 second
log_statement = 'none'           # 'all', 'ddl', 'mod', 'none'
log_lock_waits = on
log_temp_files = 0               # Log temporary files larger than this size (0 to disable)

# Autovacuum Tuning (Crucial for performance and preventing bloat)
autovacuum = on
autovacuum_max_workers = 3       # Number of worker processes
autovacuum_naptime = 1min        # How often to check for jobs
autovacuum_vacuum_threshold = 50 # Minimum number of row updates before vacuum
autovacuum_analyze_threshold = 50 # Minimum number of row updates before analyze
autovacuum_vacuum_scale_factor = 0.1 # Percentage of table size to vacuum
autovacuum_analyze_scale_factor = 0.05 # Percentage of table size to analyze

Note on AWS RDS: When using RDS, you’ll apply these settings via a custom Parameter Group. Changes to shared_buffers, work_mem, and other memory-intensive parameters often require an instance reboot to take effect.

Monitoring and Iteration

Tuning is an iterative process. Use monitoring tools to observe your system’s behavior under load:

  • Nginx: Use stub_status module for active connections, requests per second, etc.
  • Gunicorn/PHP-FPM: Monitor process counts, memory usage, and request latency. PHP-FPM has its own status page.
  • PostgreSQL: Utilize tools like pg_stat_activity, pg_stat_statements (requires extension), CloudWatch metrics (for RDS), and general OS-level monitoring (CPU, RAM, I/O).

Regularly review logs for errors, slow queries, and performance warnings. Adjust parameters incrementally and measure the impact. What works for one application might not work for another, so continuous profiling and tuning are key to maintaining optimal performance on AWS.

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