Differences between revisions 6 and 24 (spanning 18 versions)
Revision 6 as of 2020-07-11 18:05:01
Size: 4560
Comment:
Revision 24 as of 2023-05-25 17:00:50
Size: 4297
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
= PHP-FPM Setup = = PHP-FPM =
Line 3: Line 3:
'''PHP-FPM''' is a PHP implementation of the FastCGI, an enhancement of the earlier Common Gateway Interface (CGI). It works especially well with [[NGINXSetup|NGINX]].

See [[PHPFPMConfiguration|here]] for configuration of PHP-FPM.
The PHP '''FastCGI Process Manager''' ('''PHP-FPM''') is an implementation of the [[Protocols/CGI|FastCGI]] specification.
Line 15: Line 13:
To install PHP-FPM on a system, use your local package to manager to grab all of the following: `php`, `php-fpm`, `fcgiwrap`, and `nginx`. `php-fpm(8)` naturally depends on `php(1)`. See [[PHP#Installation|here]] for help with installation, and [[PHP/Configuration|here]] for help with configuration.
Line 17: Line 15:
Often `apache2-utils` (a.k.a. `apache-tools`, `httpd-utils`, etc... consult your package manager!) is also necessary, for creating `.htpasswd` files. Most [[Linux]] and [[BSD]] distributions will offer a `php-fpm` package.
Line 19: Line 17:
Upstream manages a Docker file with frequent security patching, as `bitnami/php-fpm:latest`. This will expose PHP-FPM on port 9000 and generally work out of the box. Official container images are available from the upstream development team. They are tagged like `php:<version>-fpm`

----
Line 23: Line 23:
=== PHP === == Configuration ==
Line 25: Line 25:
PHP-FPM unsurprisingly runs in '''PHP''' and will require a working installation. The primary configuration for PHP is found at `/etc/php/php.ini`. Some distributions provide two versions: a hardened `php.ini-production` and a verbose `php.ini-development`. `php-fpm(8)` listens on one or more ports or sockets. Each point of contact is a '''pool'''.
Line 27: Line 27:
See [[PHPConfiguration|here]] for help in configuring PHP. Each pool should have it's own configuration file under `/etc/php/php.fpm.d`. A pool's name (`$pool`) is derived from the section header.
Line 29: Line 29:
The upstream Docker image bundles PHP internally, but it is ''possible'' to un-bundle it and force the use of an existing installation.



=== PHP-FPM ===

For the most part, distributed configuration for PHP-FPM work out of the box.
As an example:
Line 38: Line 32:
; Pid file
pid = /run/php-fpm/php-fpm.pid
[www]
user = www-data
group = www-data
Line 41: Line 36:
; Error log
error_log = /var/log/php-fpm.log
listen = 9000
listen.allowed_clients = 127.0.0.1, 192.168.86.1

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Line 47: Line 48:
=== TCP Sockets ===
Line 48: Line 50:
=== FCGIWrap === The above example uses a shorthand: `listen = 9000`. This causes `php-fpm(8)` to listen on all addresses at port 9000.
Line 50: Line 52:
'''FCGIWrap''' is, as the name implies, a wrapper script. It manages the configuration of FastCGI through PHP-FPM so that all you need to do is point NGINX at `/run/fcgiwrap.sock`. However, `listen.allowed_clients` overrides this with a client whitelist. This should be comma-separated.
Line 52: Line 54:
At the same time, it is ''entirely'' optional. The upstream Docker image does not include it. Not using '''FCGIWrap''' will require more attention on the [[PHPFPMConfiguration|configuration of PHP-FPM]], however. An IPv4 address is specified like `127.0.0.1:9000`; an IPv6 address is specified like `[::1]:9000`.
Line 56: Line 58:
=== NGINX === === Unix Sockets ===
Line 58: Line 60:
For more details on NGINX configuration, see [[NGINXSetup|this walkthrough]]. A basic configuration for FastCGI would be: To use a [[Linux/Networking#Unix_Sockets|Unix socket]] to pass requests, try:
Line 61: Line 63:
user www-data www-data;
http {
  include mime.types;
  default_type application/mime.types;

  sendfile on;
  keepalive_timeout 65;
  gzip on;

  server {
    listen 80;
    server_name example.com;
    access_log /var/log/nginx/example.com/access.log;
    error_log /var/log/nginx/example.com/error.log;

    root /var/www;
    try_files $uri @cgi;

    location @cgi {
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $realpath_root/my-cgi-script.cgi;
      fastcgi_param PATH_INFO $uri;
      fastcgi_param QUERY_STRING $args;
      fastcgi_param HTTP_HOST $server_name;
      fastcgi_pass unix:/run/fcgiwrap.sock;
    }
  }
}
listen = /run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Line 93: Line 71:
=== Test Script === === Static Process Management ===
Line 95: Line 73:
A minimal test script to validate the PHP installation. A pool spawns and maintains `pm.max_children` number of processes.
Line 98: Line 76:
<?php phpinfo(); ?> pm = static
pm.max_children = 5
}}}



=== Dynamic Process Management ===

A pool spawns `pm.start_servers` processes. At any given time, some number are 'idle'. The pool tries to keep this number within the range of `pm.min_spare_servers` to `pm.max_spare_servers`, never surpassing `pm.max_children`.

{{{
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
}}}



=== On-Demand Process Management ===

A pool spawns processes on-demand up until `pm.max_children`. A process is killed after `pm.process_idle_timeout` has passed.

{{{
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
}}}



=== Process Termination ===

`php(1)` scripts are known to leak memory frequently. As such, a regular task is to terminate long-running processes.

A simple solution is to set `pm.max_requests` to any number other than 0. Processes are killed after handling this number of requests.



=== Hardening ===

As a security measure, the allowable script extensions should be set as strictly as possible.

{{{
security.limit_extensions = .php .html .htm
}}}

A pool can be hardened by isolating it to a distinct working directory, or even a chroot.

{{{
chroot = /absolute/path/to/chroot
# or
chdir = relative/or/absolute/path/to/working/directory
}}}

By default, `php-fpm(8)` clears all environment variables. This can be disabled, but a better strategy is to set specific environment variables.

{{{
env[HOSTNAME] = $HOSTNAME
env[TMP] = /tmp
}}}

The same is true of PHP system variables.

{{{
php_admin_value[error_log] = /var/log/php-fpm.log
Line 105: Line 149:
== Security ==

=== Work Directory ===

PHP applications can be placed anywhere on the web root and they will work as expected. This is because PHP-FPM defaults to working in the current work directory.

However, it is ''recommended'' to isolate PHP-FPM by running it in a different work directory. This is accomplished by configuring PHP-FPM on a pool level, which you can read more about [[PHPFPMConfiguration#PoolConfiguration|here]]. What needs to be addressed up-front is how a web server will interact with an isolated FastCGI environment.

The NGINX `try_files` command, as shown below, checks for existence of files. This will cause issues if PHP applications are actually living in a different directory (or a different server). However, without checking for the existence of an executable, you can run into difficult-to-debug errors and security issues regarding embedded PHP in ordinary files.

The workaround is to set the key FastCGI parameters for the target server and check the URI against local null files.

{{{
location ~ \.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_param SCRIPT_FILENAME /remote/path/to/work/directory/$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;

    try_files $uri =404;

    fastcgi_pass 192.168.86.1:9000;
    include fastcgi_params;
}
}}}

Note that `try_files` is called ''strictly after'' path info has been pulled out. Try files will, on success, overwrite `$uri` with the matched local URI.
== Usage ==
Line 134: Line 153:
=== HTTPoxy === === Nginx ===

See [[Nginx/FastCGI#PHP-FPM|here]] for details.



=== Apache ===

See [[Apache/FastCGI#PHP-FPM|here]] for details.



=== Containers ===

When containerizing a `php(1)` service, the web server should be kept separate. See other articles for help in configuring the web server containers.

A template container image recipe looks like:
Line 137: Line 172:
location ~ \.php(/|$) {
    # Mitigate https://httpoxy.org/ vulnerabilities
    fastcgi_param HTTP_PROXY "";
}
FROM php:fpm-alpine
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY myapp/ /myapp/
COPY confs/ /usr/local/etc/php-fpm.d/
EXPOSE 9000
WORKDIR /myapp
CMD php-fpm --nodaemonize
Line 142: Line 180:

By default, `php-fpm(8)` will not run as root. The `--allow-to-run-as-root` flag may need to be added.

----



== See also ==

[[https://man.archlinux.org/man/php-fpm.8|php-fpm(8)]]

PHP-FPM

The PHP FastCGI Process Manager (PHP-FPM) is an implementation of the FastCGI specification.


Installation

php-fpm(8) naturally depends on php(1). See here for help with installation, and here for help with configuration.

Most Linux and BSD distributions will offer a php-fpm package.

Official container images are available from the upstream development team. They are tagged like php:<version>-fpm


Configuration

php-fpm(8) listens on one or more ports or sockets. Each point of contact is a pool.

Each pool should have it's own configuration file under /etc/php/php.fpm.d. A pool's name ($pool) is derived from the section header.

As an example:

[www]
user = www-data
group = www-data

listen = 9000
listen.allowed_clients = 127.0.0.1, 192.168.86.1

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

TCP Sockets

The above example uses a shorthand: listen = 9000. This causes php-fpm(8) to listen on all addresses at port 9000.

However, listen.allowed_clients overrides this with a client whitelist. This should be comma-separated.

An IPv4 address is specified like 127.0.0.1:9000; an IPv6 address is specified like [::1]:9000.

Unix Sockets

To use a Unix socket to pass requests, try:

listen = /run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Static Process Management

A pool spawns and maintains pm.max_children number of processes.

pm = static
pm.max_children = 5

Dynamic Process Management

A pool spawns pm.start_servers processes. At any given time, some number are 'idle'. The pool tries to keep this number within the range of pm.min_spare_servers to pm.max_spare_servers, never surpassing pm.max_children.

pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

On-Demand Process Management

A pool spawns processes on-demand up until pm.max_children. A process is killed after pm.process_idle_timeout has passed.

pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s

Process Termination

php(1) scripts are known to leak memory frequently. As such, a regular task is to terminate long-running processes.

A simple solution is to set pm.max_requests to any number other than 0. Processes are killed after handling this number of requests.

Hardening

As a security measure, the allowable script extensions should be set as strictly as possible.

security.limit_extensions = .php .html .htm

A pool can be hardened by isolating it to a distinct working directory, or even a chroot.

chroot = /absolute/path/to/chroot
# or
chdir = relative/or/absolute/path/to/working/directory

By default, php-fpm(8) clears all environment variables. This can be disabled, but a better strategy is to set specific environment variables.

env[HOSTNAME] = $HOSTNAME
env[TMP] = /tmp

The same is true of PHP system variables.

php_admin_value[error_log] = /var/log/php-fpm.log


Usage

Nginx

See here for details.

Apache

See here for details.

Containers

When containerizing a php(1) service, the web server should be kept separate. See other articles for help in configuring the web server containers.

A template container image recipe looks like:

FROM php:fpm-alpine
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY myapp/ /myapp/
COPY confs/ /usr/local/etc/php-fpm.d/
EXPOSE 9000
WORKDIR /myapp
CMD php-fpm --nodaemonize

By default, php-fpm(8) will not run as root. The --allow-to-run-as-root flag may need to be added.


See also

php-fpm(8)


CategoryRicottone

PHP/FPM (last edited 2023-05-25 17:00:50 by DominicRicottone)