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.
Contents
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.