Tuning PHP-FPM

How it started -
I recently switched to Linode for a basic VPS (512 MB RAM, 20 GB storage). I wanted to be able to install absolutely what ever version of what ever software I wanted. Since I have become very familiar with Arch Linux (I run it on all of my home computers now) I decided to make that my server OS for the time being. Along with being lightweight, minimalistic and always up-to-date arch offers an awesome set of packages and the best package manager I have ever used. Par of Arch Linux’s appeal is the Arch Wiki. A repository of important information for installing and configuring packages for Arch. So when following the guide for nginx, I also installed PHP-FPM for PHP/CGI.

All is well until weeks later I notice my memory usage tends to balloon after a few days (some times hours).  After hours of toying, poking, prodding, and messing with WordPress settings, I figured out that PHP-FPM was the culprit. At times consuming 300+ MB of memory! Something had to be done, but what? After a good bit of digging and a few key parameter changes later PHP-FPM was well within the memory constraints of my server and website performance became noticeably faster!The Problem -

First we need to take a look at the default config file for PHP-FPM. You are going to want to find the section that looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives:
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes to be created when pm is set to 'dynamic'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either 'static' or 'dynamic'
; Note: This value is mandatory.
pm.max_children = 60

; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 20

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 20

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 35

; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
pm.max_requests = 500

Here, if you read the descriptions you get a basic idea of what these options are for. If we take a look at the line  pm.max_children = 60 you see that the max “children” is a nice and high 60. This means that PHP-FPM will spawn a max of 60 child processes each requiring some amount of memory.

The next line to look at is  pm.start_servers = 20 This shows us that there will be 20 server processes started.

Both  pm.min_spare_servers = 20 and  pm.max_spare_servers = 20 limit how many resources are consumed when the server is idle, you want at least a few processes available in case a spike happens but you don’t need a bunch sitting around after one dies off.

Finally the last line we need to tweak is  pm.max_requests = 500 This causes processes to be killed off and re-started every 50 requests regardless if they need to be. This is perfect for fighting memory leaks in something like a poorly written WordPress plugin.

Now these would be considered sane defaults if our server was a bit more beefy (at least 1 GB of ram) but because were in such a limited environment, way to many PHP processes are getting spawned and combined with local caching the whole thing is a bit frustrating. So what can we do to fix it?

The Solution -

Looking at the file and better understanding what the options mean is a great step but what numbers should be used? Well there really is little information on what is right because there are way to many variables to take this into account. However, lowering all these variables to more sane values will sure help a lot in the total memory consumption of PHP-FPM and the responsiveness of your server.

Currently here are the values that I am using

1
2
3
4
5
6
pm = dynamic
pm.max_children = 30
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 50

You want to make sure you are using pm = dynamic management. With out it, it will just use a fixed size (fine but not necessary and makes some settings pointless).

After some testing, the server seems to idle at around ~140 MB of memory (for everything) which is much improved over the ~420 MB it was using previously.

The goal here is to find values that work for you. I plan on testing my site with Apache JMeter for a better idea just how significant the savings are in terms of page load times.

There are 8 Comments to "Tuning PHP-FPM"

  • john says:

    what happens if you add APC in the game? is memory shared?

    • NoNeedForAName says:

      I can confirm OP’s observation of the resulting footprint of ~140.
      # free
      total used free shared buffers cached
      Mem: 512 245 266 0 0 101
      -/+ buffers/cache: 144 367
      Swap: 512 0 511

      CentOS 6.x (2.6.32-042stab063.2 #1 SMP Tue Oct 23 16:24:09 MSK 2012 x86_64), 512MB OpenVZ VPS w/ Nginx 1.2.6, php53u-fpm.x86_64 5.3.19-1.ius.el6 via *nix sockets, APC enabled @ defaults.

      ~0 load, but this ‘lil VPS just flies. Thanks for the tips, OP.

  • zhangxiao says:

    Exactly the trouble I am dealing with and very help. thank you very much!

  • A bit late, but for completeness…

    Yes, APC shares it’s cache between all children when running php-fpm, making it a major performance enhancement.

  • [...] Then you can do some fine tuning to the /etc/php5/fpm/php5-fpm.conf  to fit your server setup. There’s a great post about it here. [...]

  • Roelven says:

    Checking to see if this is not a typo: with

    1
    pm.max_requests = 500

    you mean the process gets respawned after every 500 requests and not 50 correct?

    Great writeup!

  • Chowhan says:

    I’m a long time watcher and I just considered I’d drop by and say howdy there for the really initial time.

Write a Comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>