Back to articles
InfrastructureMay 202611 min

NGINX + PHP-FPM in production: the configuration decisions that actually matter

Most NGINX tutorials show you how to get something running. This one explains why the defaults will hurt you at scale — worker processes, keepalive, buffer sizes, upstream timeouts, and the one fastcgi_param that breaks half of Laravel deployments.

Standard Linux distributions ship web server configurations designed for compatibility, not high-performance execution. When running high-throughput PHP-FPM workloads behind an NGINX proxy, leaving settings at their default values causes frequent 502 Bad Gateway and 504 Gateway Timeout errors under sustained concurrent load.

Tuning PHP-FPM Pool Limits

The most common failure mode under peak traffic is child process exhaustion. When the PHP-FPM worker pool reaches its limit, subsequent requests queue up in the socket backlog until the NGINX upstream timeout expires.

Static vs. Dynamic Allocation

For dedicated application servers, dynamic pool scaling introduces unnecessary process spawning overhead. Use static process management to maintain a warm pool of persistent workers, reducing CPU spikes during sudden traffic surges.

/etc/php/8.3/fpm/pool.d/www.conf
; Choose static process manager for dedicated environments
pm = static

; Exact number of concurrent child processes to keep warm
pm.max_children = 80

; Prevent slow memory leaks by cycling processes after 10,000 executions
pm.max_requests = 10000

; Clear environment variables to isolate process execution
clear_env = no

Memory Math

To determine your system's pm.max_children capacity, use this direct memory budget formula:

  • Establish the absolute memory consumed by the host OS and base services (typically 1.5GB to 2GB).
  • If the database runs on the same instance, allocate its full InnoDB buffer pool memory budget.
  • Subtract these values from total system RAM to find the available PHP execution budget.
  • Divide this available budget by the average memory footprint of your loaded PHP processes (measured via real peak memory usage).

Example: On a 16GB virtual instance with a local MySQL server utilizing 6GB, and the OS consuming 2GB, we have 8GB available for PHP. If the application's average memory footprint is 80MB per worker, the calculations dictate a safe maximum of 100 workers (8192MB / 80MB).

Overriding Web Server Buffers

NGINX processes backend responses in chunks. If a PHP script returns a large payload (such as serialized JSON models or heavy operational tables) that exceeds NGINX's default FastCGI buffer sizes, NGINX is forced to write temporary data to the server disk, introducing severe latency.

/etc/nginx/nginx.conf
http {
    # Increase buffers to hold larger application payloads in memory
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    fastcgi_busy_buffers_size 64k;
    fastcgi_temp_file_write_size 64k;

    # Extend timeouts to accommodate complex reporting queries
    fastcgi_read_timeout 180s;
    fastcgi_send_timeout 180s;
    
    # Disable buffering for real-time Server-Sent Events (SSE)
    fastcgi_buffering off;
}

The FastCGI Parameter Trap

A silent configuration error occurs when NGINX fails to forward authentic HTTPS headers to PHP-FPM. If your SSL decryption happens at an upstream edge proxy (like Cloudflare or an external gateway) and you pass unmapped variables down, PHP fails to detect the secure layer, causing routing frameworks to generate broken HTTP assets and mixed-content warnings.

/etc/nginx/fastcgi_params
# Map proxy scheme headers down to the FastCGI environment
fastcgi_param  HTTPS             $fcgi_https if_not_empty;
fastcgi_param  HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;

# Set correct host headers to prevent host spoofing vulnerabilities
fastcgi_param  HTTP_HOST          $http_host;
Written By
SK
Sagar Kapasi
Software Engineer

Sagar builds operational systems and developer hosting infrastructure from the ground up, specializing in Linux, PHP, and high-performance architectures.

Tags
NGINXPHPLinuxProduction