step 6: install and configure php5, php-fpm and apc

php-fpm logo

We have just installed Nginx and now we want to host the php application we made. This is when php-fpm comes into play. Since the day i started playing around with php-fpm i loved it. You install php-fpm and then using the powerful configuration files you can setup multiple vhost on a single machine. Each php-fpm pool, can have it's own socket or run on it's own port, you can setup a different user and group for each pool and put as many ftp-users in that group as you want. You can even chroot the directory you as nginx public web directory, have an own php-ini per vhost and therefore also setup a different sessions folder for each pool.

install php5 and php-fpm:

php-fpm (PHP fastCGI process manager) is available in the remy repo which depends on the epel repo:

I wanted the remi repo "Enterprise Linux 6 (with EPEL)" package, you may have to use another one depending on your OS and version, i used centos6 and therefore choosed to use the following commands:


# rpm --import https://fedoraproject.org/static/0608B895.txt
# rpm --import http://rpms.famillecollet.com/RPM-GPG-KEY-remi
# wget http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-7.noarch.rpm
# wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
# rpm -Uvh remi-release-6*.rpm epel-release-6*.rpm

edit the remi repo file using the vi editor to change the line enabled from 0 to 1:

first open the directory with all the repositories files

# cd /etc/yum.repos.d

list all the files in that directory, using the following command

# ls -l

now there should be a file in the list that has the name remi.repo, open the remi repo file with vi

# vi remi.repo

press the "i" key to go into edit mode

change enabled from 0 to 1

press the escape key to exit from the edit mode

type :x! to save and exit

or type :q! to quit without saving

to install php and php-fpm and some php packages, type:

# yum -y install php php-fpm php-pear php-gd php-mbstring php-mcrypt php-xml

you can add whatever other packages you may need, or you install them later, like for example php-magickwand, php-tidy, php-pecl-memcache, ...

add php-fpm to the startup scripts:

# chkconfig --levels 235 php-fpm on

create a user account and a group

create a group that is allowed to access the nginx public web folder, put the php-fpm and the ftp users of your vhosts in that group:

create a user named "www-vhost-user" or whatever you want:

# useradd -d /usr/share/nginx/html -s /usr/sbin/nologin www-vhost-user

! if you want to host multiple vhost on your server, it's advised to create one user per vhost and one group per vhost, but remember to add all the vhost ftp users to the vhost group.

now we create a group called "www-vhost-group" or whatever name you prefer:

# groupadd www-vhost-group

finally we add the supplementary group "www-vhost-group" to our user "www-vhost-user":

# usermod -aG www-vhost-group www-vhost-user

now we give that new group permissions for the web folder using the chown command:

# chown www-vhost-user:www-vhost-group /usr/share/nginx/html/my_vhost

the default sessions folder will have as group owner "apache" set, but the group we just created is "www-vhost-group", use this command to change the owners:

# chown www-vhost-user:www-vhost-group /var/lib/php/session

! if you want to set up multiple vhosts/php-fpm pools, then you should create multiple session sub-folders, one for each vhost, if you don't use the default folder don't forget to afterwards change the "session.save_path" in your php.ini

! take care: if you choose to use the default session folder, you will have to redo this last step every time you do a "yum update" during which yum updates your php package, because every update of php will reset ownership of the default session folder back to the default value which is "apache"

change the permissions of the folder to 775 so that everybody in the group "www-vhost-group" can write, read and excute files in that folder:

# chmod 775 /usr/share/nginx/html/my_vhost_name

configure php-fpm

php-fpm uses pools to manage multiple websites on one server, you use different user accounts for each pool, allocate resources and so on ...

i recommend that you setup one pool per web application you host, the default pool that php-fpm has setup for you is www, we will use that one:

find the php-fpm www pool configuration file:

# locate www.conf

default location probably is:

/etc/php-fpm.d/www.conf

edit the php-fpm www.conf:

# vi /etc/php-fpm.d/www.conf

[www]

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses on a
;                            specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000

; Set listen(2) backlog. A value of '-1' means unlimited.
; Default Value: -1
;listen.backlog = -1

; List of ipv4 addresses of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
listen.allowed_clients = 127.0.0.1

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
;                 mode is set to 0666
;listen.owner = www-vhost-user
;listen.group = www-vhost-group
;listen.mode = 0666

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache Choosed to be able to access some dir as httpd
;user = apache
user = php
; RPM: Keep a group allowed to write in log dir.
;group = apache
group = www-vhost-group

; 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 = 50

; 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 = 5

; 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 = 5

; 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

; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. By default, the status page shows the following
; information:
;   accepted conn    - the number of request accepted by the pool;
;   pool             - the name of the pool;
;   process manager  - static or dynamic;
;   idle processes   - the number of idle processes;
;   active processes - the number of active processes;
;   total processes  - the number of idle + active processes.
; The values of 'idle processes', 'active processes' and 'total processes' are
; updated each second. The value of 'accepted conn' is updated in real time.
; Example output:
;   accepted conn:   12073
;   pool:             www
;   process manager:  static
;   idle processes:   35
;   active processes: 65
;   total processes:  100
; By default the status page output is formatted as text/plain. Passing either
; 'html' or 'json' as a query string will return the corresponding output
; syntax. Example:
;   http://www.foo.bar/status
;   http://www.foo.bar/status?json
;   http://www.foo.bar/status?html
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status

; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; - create a graph of FPM availability (rrd or such);
; - remove a server from a group if it is not responding (load balancing);
; - trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
;       anything, but it may not be a good idea to use the .php extension or it
;       may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping

; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong

; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0

; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0

; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
slowlog = /var/log/php-fpm/www-slow.log

; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024

; Set max core size rlimit.
; Possible Values: 'unlimited' or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0

; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: chrooting is a great security feature and should be used whenever
;       possible. However, all PHP paths will be relative to the chroot
;       (error_log, sessions.save_path, ...).
; Default Value: not set
;chroot =

; Chdir to this directory at the start. This value must be an absolute path.
; Default Value: current directory or / when chroot
;chdir = /var/www

; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Default Value: no
;catch_workers_output = yes

; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; execute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
security.limit_extensions = .php

; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
;   php_value/php_flag             - you can set classic ini defines which can
;                                    be overwritten from PHP call 'ini_set'.
;   php_admin_value/php_admin_flag - these directives won't be overwritten by
;                                     PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.

; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.

; Default Value: nothing is defined by default except the values in php.ini and
;                specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

the php-fpm is well commented, but you still could check out the php-fpm options page on php.net to get an overview of the available options

the option i uncommented was the security.limit_extensions options, i have set it like this to only allow files with the extention "dot php" to get executed:

security.limit_extensions = .php

finally we restart php-fpm:

# service php-fpm restart

launch php-fpm

# service php-fpm start

to restart php-fpm type

# service php-fpm restart

speed up php-fpm by using the socket instead of the port

we want to use the php-fpm socket instead of the port, for that we must edit the php-fpm www.conf (or whatever name you gave your pool file):

# vi /etc/php-fpm.d/www.conf

comment out the listen line:

;listen = 127.0.0.1:9000

then add this new line just below:

listen = /tmp/php-fpm.sock

last step is to set the user, group and permission of your socket file

uncomment those three lines and use the values you want, or if you followed this guide step by step use these values:

listen.owner = www-vhost-user
listen.group = www-vhost-group
listen.mode = 0666

this tells php-fpm to put a socket file in our tmp folder, instead of listening to the default port 9000

after every change to your configuration restart php-fpm

# service php-fpm restart

now php-fpm should have created a socket file for your

open your tmp directory

# cd /tmp

now list the files in that directory

# ls -l

you should see one called "php-fpm.sock"

next step is to edit our nginx vhost configuration file, to tell nginx that php-fpm is now available via socket instead of the port:

we edit the vhost file we previously created

# vi /etc/nginx/conf.d/my_first_vhost.conf

comment out the line:

#fastcgi_pass   127.0.0.1:9000;

and just below add this new line:

fastcgi_pass   unix:/tmp/php-fpm.sock;

to restart nginx type

# service nginx restart

install the apc extension:

if you want you can also install APC (Alternative PHP Cache) which is an opcode cache for php and also has an API to cache database request or similar data as cache in your server memory:

# yum install php-pecl-apc

to restart php-fpm type

# service php-fpm restart

to restart nginx type

# service nginx restart

!if you get a segmentation fault error messages when starting php-fpm, reduce the amount of worker processes (cpu cores) in the nginx.conf and retry to start php-fpm, apc seems to need at least one core for itself, so if you tell nginx to use all your cores you may get this segmentation fault error:

tweak the php.ini file

you may also want to tweak your php.ini and for example add some base_dir restrictions

# vi /etc/php.ini

change the line cgi.fix_pathinfo to 0 (as recommended on the nginx pitfalls page):

cgi.fix_pathinfo=0;

a good read if you want to know why you should change this is documented in this blog article

uncomment the open_basedir line and change it to something like this:

open_basedir = /usr/share/nginx/html:/usr/share/pear:/var/lib/php/session:/tmp

don't forget to set the timezone correctly

date.timezone = "Europe/Luxembourg"

you could also add function names to the disable_functions list or the disable_classes, for example like this:

disable_functions = eval, exec, passthru, shell_exec, system, proc_open, popen, show_source

try to add all the functions of php that seem to be dangerous like "system" but that you don't require in your scripts

these disabled functions and classes will not be available, your scripts or somebody that hacked your machine won't be able to use them in it's scripts

in the nginx configuration we disabled the option "server tokens", in php there is a similar functionality you should use, to avoid giving people too much information about the exact version of a software that you use, in php it is called "expose php", ensure it is off:

expose_php = Off

yet another option you might adjust to your need is the maximum amount of memory a script is allowed to use, called memory limit:

memory_limit = 256M

don't set it too high as you may run out of memory soon, but also too low as you may cause error messages to appear because some of your php scripts run out of memory, you could use a monitoring tool to get an idea what the memory peaks of your scripts are or you use the php function "memory_get_peak_usage" and log that information

as we setup a production server the two options "display errors" and "display startup errors" should be set to off, we don't want to display any errors and also error data like the tracert output, that may make information about your server available to third parties

display_errors = Off
display_startup_errors = Off

but instead of displaying errors you may want to log them, check out your logs from time to time to see if there are no new errors messages that appear since your latest website update:

log_errors = On

post_max_size and "upload_max_filesize" are two options you may want to adapt to your needs, the first one is the maximum allowed size a post request can have, including eventually an upload, that's why the post max size should be bigger then the upload_max_filesize value:

post_max_size = 64M
upload_max_filesize = 60M

another two options that you should disable if your scripts don't require them are "allow_url_fopen" and "allow_url_include":

allow_url_fopen = Off
allow_url_include = Off

yet another option you may want to set is the default charset option

default_charset = "utf-8"

if you don't use the default session path you need to change it too in your php.ini:

session.save_path = "/var/lib/php/session/my_vhost_sessions_directory"

do i need to mention that magic_quotes_gpc should be off, what it is by default, no i don?t think so ;)

magic_quotes_gpc = Off