Tweaking NGINX and PHP-FPM configuration to fix 502 Bad Gateway errors and optimise performance on your server

Having recently migrated a number of servers from Apache to NGINX for use in a high-traffic environment, I thought I’d put together this article to help others adjust ‘out the box’ settings with a view to maximising performance and prevent those annoying 502 Bad Gateway errors.

What is a 502 error?

The official definition is somewhat confusing / misleading…

“The server was acting as a gateway or proxy and received an invalid response from the upstream server”

In the case of NGINX / PHP-FPM, what this actually means is that NGINX has sent a request to PHP-FPM to be processed, but has not received a response. This is most likely because PHP-FPM has one of a few problems (a) run out of memory, (b) the socket has broken, (c) the limits / timeouts / buffers are too tight and it’s not responding in time.

More memory won’t necessarily help you

Typically you might adding memory to the server might solve your problem. However PHP-FPM’s ‘out the box’ configuration contains some pretty conservative process management preferences, essentially ‘throttling’ it regardless of the hardware setup.

By making some small tweaks to the PHP-FPM and NGINX configurations, you can utilise the resources available & subsequently solve the 502 problem. Specifically I’m going to show you how to:

  1. Increase the timeouts and buffers between NGINX and PHP-FPM.
  2. Change the way NGINX connects to PHP-FPM to be more scalable in high traffic applications.
  3. Tweak PHP-FPM’s process management configuration to utilise the available memory on the server, compensate for memory leaks and scale nicely.

How to increase the timeouts and buffers between NGINX and PHP-FPM.

Increasing the buffers and timeouts gives NGINX / PHP-FPM space to work, particularly if you have any heavy PHP scripts.

In the http or location block of your NGINX site configuration, add the following to increase buffers and timeouts:

location {
...
  fastcgi_buffers 8 16k; # increase the buffer size for PHP-FTP
fastcgi_buffer_size 32k; # increase the buffer size for PHP-FTP
fastcgi_connect_timeout 60;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
}

You’ll need to reload NGINX afterwards from the command line:

sudo service nginx reload

How to change the way NGINX connects to PHP-FPM:

NGINX can connect to PHP-FPM in two ways:

  • A file on disk that acting as a ‘socket’
  • Through a TCP/IP socket.

Theoretically, a file socket saved on disk would be faster (no TCP/IP overhead), but my benchmarking showed the difference to be negligible and in my experience, TCP/IP seems to scale a lot better than a file socket.

To switch them out you need to change both the NGINX configuration (to tell it where to send requests), and the PHP-FPM configuration (to tell if where to listen for requests):

In your NGINX site configuration, change instances of the following

fastcgi_pass unix:/var/run/php5-fpm.sock;

; to this (assuming PHP-FPM is running on the default port 9000):

fastcgi_pass 127.0.0.1:9000;

This tells NGINX to direct all requests to the TCP/IP socket rather than the file socket on disk.

In your PHP-FPM configuration (the www.conf file) you need to change the following:

listen = /var/run/php5-fpm.sock

; to this:

listen = 127.0.0.1:9000

This tells PHP-FPM to listen on the TCP/IP socket.

You’ll need to reload both PHP-FPM and NGINX afterwards from the command line:

sudo service nginx reload
sudo service php5-fpm reload

How to tweak PHP-FPM’s process management configuration

Out of the box, PHP-FPM has very modest settings allowing it to run on low spec hardware. In reality most modern web servers have a fair bit of memory at their disposal, so by tweaking the configuration you can take advantage of that extra horsepower.

The first thing to understand is that PHP-FPM works by spooling up a number of ‘worker’ processes that sit there idle waiting for NGINX to feed in requests to process. There is one ‘master’ process and a bunch of ‘child’ processes. You can use a utility like htop to see them:

The first thing you want to do is work out how much memory each PHP process is using.

In my screenshot above you can see that each process is using between 13-30mb of memory. This server has 3,953mb of memory and doesn’t run anything except NGINX and PHP-FPM (if you run something else like MySQL / Memcached / Redis you need to leave memory for that too). So being cautious and leaving 1gb of RAM for the OS and NGINX, we can calculate:

(3953–1024) / 30 = 97.63

So we could potentially run 97 simultaneous PHP-FPM processes without exhausting available physical memory.

Now we can configure PHP-FPM in www.conf

PHP-FPM has three options for process management: static, dynamic and ondemand. The default is dynamic and for the purpose of this article that’s what we’ll be configuring.

pm = dynamic

There are four key settings we want to change:

pm.max_children = 97
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 200

Let’s break this down:

  • pm.max_children : this is the maximum number of processes PHP-FPM will spool up. This is derived from our calculation above. This is the hard limit and absolute maximum number of processes that will ever be started by PHP-FPM.
  • pm.start_servers : this is the number of ‘spare’ processes we want PHP-FPM to spool up when it starts — processes ready and waiting to process requests. Each one will use memory and it takes time to spool up new processes, so I chose 20 as a start point
  • pm.min_spare_servers : this is the minimum number of spare processes we want available (where possible). I chose 10, so if all 20 of my initial pm.start_servers are busy the server will spool up another 10 meaning 30 running in total.
  • pm.max_spare_servers : this is the maximum number of spare processes we want available.
  • pm.max_requests : this one is particularly useful. This number specifies how many requests each process should handle before is kills itself and re-spawns. If your code (or 3rd party libraries) has memory leaks, eventually PHP-FPM will eat up your physical RAM and crash (bringing your website down with a 502). By setting this, PHP-FPM continuously recreates itself and prevents the ‘memory leak creep’ problem bringing down your web server.

When you’re done you’ll need to restart PHP-FPM again:

sudo service php5-fpm reload

If you follow the steps above, your application should be able to take advantage of your server’s resources, prevent problems and manage your cost (ie. lower spec servers needed) at the same time.

It’s a bit of a minefield online looking at this kind of stuff but here’s some other useful resources, some with a little more detail than I’ve gone into: