Skip to content

Ansible Role linuxfabrik.lfops.php

This role installs and configures PHP (and PHP-FPM) on the system, optionally with additional modules.

By default this role does not select a PHP version. It installs the latest version the configured repos offer. On RedHat that is deterministic, because the module stream pins the version at repo level: use linuxfabrik.lfops.repo_remi beforehand to choose it.

Debian has no repo-level equivalent. Its unversioned metapackages (php-cli, php-fpm, php-curl, ...) point at whatever the configured repo declares as its default, which never moves within a Debian release but does move with the sury repo, whenever sury promotes a new PHP version. On a sury host an ordinary apt upgrade therefore migrates PHP to a new major version without anyone deciding to. Set php__version to prevent that.

Consuming roles that inject php__modules__dependent_var on Debian must build the package names from the detected version, for example php{{ __php__installed_version }}-curl, because the unversioned names would reintroduce exactly that drift. See roles/nextcloud/vars/main.yml for the platform-keyed pattern.

This role is compatible with the following PHP versions:

  • 7.2
  • 7.3
  • 7.4
  • 8.0
  • 8.1
  • 8.2
  • 8.3
  • 8.4
  • 8.5

Rules of thumb:

  • Specify memory values in MB (M).
  • memory_limit should be larger than post_max_size.
  • post_max_size can stay at 16M, even if you have upload_max_filesize > 10000M for example.
  • If disabling opcache.validate_timestamps, opcache.revalidate_freq is ignored.

This role never exposes to the world that PHP is installed on the server, no matter what.

Available since LFOps 2.0.0.

How the Role Behaves

  • On RedHat the role ships a small SELinux policy module, lfops_php_fpm_slowlog, and hands it to the selinux role through php__selinux__modules__dependent_var. It grants the httpd_t domain the sys_ptrace capability and ptrace on itself, which the PHP-FPM master needs to read the backtrace out of a worker that exceeded php__fpm_pool_conf_request_slowlog_timeout__*_var. Without it, PHP-FPM logs failed to ptrace(ATTACH) child <pid>: Operation not permitted (1) and leaves the slowlog empty. The targeted policy grants neither permission and offers no boolean for it, so the rules have to come from a module.
  • The module is installed regardless of the configured request_slowlog_timeout, so that turning the slowlog on later is a pure configuration change. The permissions it grants apply to the whole httpd_t domain, Apache httpd included. Its rules are unconditional and therefore not subject to the deny_ptrace boolean: on a host hardened with setsebool -P deny_ptrace on, httpd_t can still ptrace itself. Set php__skip_selinux: true in the playbook to leave the host's policy untouched.
  • Every pool using the default files session handler gets a dedicated session directory below the distribution's session base (/var/lib/php/session on RedHat, /var/lib/php/sessions on Debian), owned by the pool's user and group with mode 0700, so pools cannot read each other's sessions. On RedHat the /var/lib/php/session(/.*)? file context gives it the httpd_var_run_t type php-fpm needs. On Debian the packaged sessionclean timer recurses the session base using the global session.gc_maxlifetime, so a per-pool session.gc_maxlifetime is not honored by the cleanup there, and a session that stays open but idle longer than the lifetime may be removed.
  • Each pool writes its error_log and slowlog into a per-service log directory (/var/log/php-fpm on RedHat, /var/log/<service> on Debian, e.g. /var/log/php8.4-fpm), which the role creates. On RedHat the package's logrotate config already rotates /var/log/php-fpm/*log; on Debian the role ships /etc/logrotate.d/linuxfabrik-php-fpm for the per-pool logs, since the packaged config only covers the single global log file.
  • The [global] section of the PHP-FPM configuration is deployed as z00-linuxfabrik-global.conf next to the pools, because php-fpm.conf itself belongs to the package. Which side wins depends on where the packaged php-fpm.conf puts its include= line, and the families differ: RedHat reads the pool directory before its own [global], so the package overrides the drop-in, while Debian reads it after and the drop-in overrides the package. The role therefore sets only directives that no packaged php-fpm.conf touches (log_level and the emergency_restart_* pair), which take effect on both. error_log, pid and daemonize are deliberately left out: they are exactly what the packages set, so setting them here would move them on Debian and silently do nothing on RedHat.
  • With pm = dynamic the master checks once per second whether pm.min_spare_servers workers are idle. If not, it forks a batch of workers and doubles the batch size for the next check, up to pm.max_spawn_rate (32). From a batch size of 8 on, every one of those checks logs seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers) at warning level, and unlike the server reached pm.max_children setting warning beside it, this one has no once-only guard, so it repeats every second for as long as the shortfall lasts. The batch size falls back to 1 as soon as the pool has pm.min_spare_servers idle workers again, and also when pm.max_children is reached, so the warning needs four consecutive seconds of shortfall before it appears at all. An ordinary traffic spike therefore writes a block of these warnings while the pool still had capacity to spare: server reached pm.max_children setting is the saturation signal, seems busy only a hint that the pool could not refill its idle reserve fast enough. Raising php__fpm_pool_conf_pm_start_servers__*_var and php__fpm_pool_conf_pm_min_spare_servers__*_var does that, at the cost of that many resident workers per pool; on a host where spikes are normal, pass --ignore='seems busy' to the php-fpm-logfile Monitoring Plugin instead.
  • Each pool listens on its own Unix socket below the FPM runtime directory (/run/php-fpm/<pool>.sock on RedHat, /run/php/<pool>.sock on Debian). The socket belongs to root and carries a POSIX ACL entry for the web server user (listen.acl_users), so a pool running as its own user still hands the web server access without either of them owning the socket. On Debian this deviates from the packaged pool file, which uses listen.owner / listen.group instead. On Debian the packaged php-fpm systemd unit additionally maintains a version-agnostic update-alternatives alias at /run/php/php-fpm.sock pointing at the socket of the default www pool. That alias only ever tracks www, so configure the web server with the explicit per-pool socket path rather than the generic one. RedHat ships no such alias.
  • The role orders Apache after PHP-FPM at boot with the drop-in /etc/systemd/system/httpd.service.d/z00-php.conf (apache2.service.d on Debian). The RHEL package only makes httpd pull in php-fpm and Debian does not even that, so both otherwise start side by side, and the first requests after a reboot fail with AH02454: FCGI: attempt to connect to Unix domain socket ... failed until the PHP-FPM socket exists. On a host without Apache the ordering is ignored. It takes effect at the next boot, without a restart.
  • The pool sets env[PATH], which upstream leaves unset: with clear_env at its default the worker environment is empty, so getenv("PATH") returns nothing, which trips applications that shell out and fails Nextcloud's "PHP getenv" setup check.
  • Every pool gets its own WSDL cache directory below /var/lib/php/wsdlcache, owned like its session directory. The SOAP extension caches parsed WSDL files there, so a SoapClient does not refetch and reparse the service description on every request. PHP's own default is /tmp, which on RedHat means the php-fpm unit's PrivateTmp namespace and therefore a cache thrown away on every restart, and on Debian the shared /tmp. Only relevant to applications using SoapClient: without the soap extension installed the setting is inert, and ini_get('soap.wsdl_cache_dir') returns an empty string.
  • Every pool answers on the same two FPM-internal endpoints, /fpm-status (the status page) and /fpm-ping (a liveness check returning pong). Which pool answers is decided by the socket the request arrives on, not by the path, so a second pool is published by giving it its own Location pointing at that pool's socket while keeping /fpm-status as the path sent to FPM. The localhost vHost of the apache_httpd role does this for the www pool, which is what the php-fpm-status and php-fpm-ping Monitoring Plugins check by default; php-fpm-status accepts --url several times for the additional pools. Both endpoints are reachable for anyone who can reach the pool socket, so take care with a vHost that forwards its whole URI space to a pool, or with a reverse proxy that passes unknown paths through. Set pm_status_path and / or ping_path to an empty string to turn them off for a pool.

Known Limitations

  • Setting a pool to state: 'absent' removes its pool configuration file, but leaves its session directory and its error_log / slowlog behind. Remove them by hand once the pool is gone for good.

Dependent Roles

Any LFOps playbook that installs this role runs these for you. Optional ones can be disabled via the playbook's skip variables.

Tags

php

  • Installs php, php-fpm and composer.
  • Installs and removes the configured PHP modules.
  • Deploys the z00-linuxfabrik.ini for every SAPI.
  • Deploys and removes the PHP-FPM pools and the [global] drop-in, together with their session, opcache and log directories.
  • Deploys the systemd drop-in that orders Apache after PHP-FPM at boot.
  • Deploys the logrotate configuration for the per-pool logs (Debian only).
  • Manages the state of the php-fpm service.
  • Pins the php, phar and phar.phar alternatives (Debian with php__version set only).
  • Triggers: php-fpm.service restart.

php:alternatives

  • Debian with php__version set only. Pins the php, phar and phar.phar alternatives to the declared version, so installing another version does not silently switch the CLI.
  • Triggers: none.

php:fpm

  • Deploys and removes the PHP-FPM pools, and the [global] drop-in next to them. On Debian these live under the declared version's tree, on RedHat under /etc/php-fpm.d.
  • Deploys the systemd drop-in that orders Apache after PHP-FPM at boot.
  • Creates the shared opcache directory, the php-fpm log directory and one session directory per pool, and relabels them on SELinux hosts.
  • Triggers: php-fpm.service restart.

php:ini

  • Deploys the z00-linuxfabrik.ini. RedHat has a single /etc/php.d, Debian one conf.d per SAPI (apache2, cli and fpm) below the declared version's tree.
  • Deploys the PHP-FPM pools, since they take over several php__ini_* values, such as memory_limit.
  • Triggers: php-fpm.service restart.

php:logrotate

  • Debian only. Deploys /etc/logrotate.d/linuxfabrik-php-fpm for the per-pool logs. On RedHat the packaged logrotate configuration already covers them.
  • Triggers: none.

php:modules

  • Installs and removes the PHP modules from php__modules__combined_var.
  • Triggers: none.

php:state

  • Enables or disables the php-fpm service and brings it into the state requested by php__fpm_service_state.
  • Triggers: none.

php:update

  • Updates the PHP packages, composer and the PHP modules, and reasserts the ini, the pools and their [global] drop-in, the logrotate configuration, the service state and the alternatives. Do not forget to update the repo beforehand.
  • On Debian with php__version set, this is also how a major version change is carried out: raise php__version, then run this tag. It installs the declared version, moves the pools, alternatives and the FPM service over to it, and purges the stacks of all other versions.
  • Triggers: php-fpm.service restart.

Optional Role Variables

php__fpm_service_enabled

  • Enables or disables the php-fpm service, analogous to systemctl enable/disable.
  • Type: Bool.
  • Default: true

php__fpm_service_state

  • Changes the state of the php-fpm service, analogous to systemctl start/stop/restart/reload.
  • Type: String. One of reloaded, restarted, started, stopped.
  • Default: 'started' if php__fpm_service_enabled is true, else 'stopped'

php__modules__host_var / php__modules__group_var

  • List of dictionaries containing additional PHP modules that should be installed via the standard package manager.
  • For the usage in host_vars / group_vars (can only be used in one group at a time).
  • Type: List of dictionaries.
  • Default: []
  • Subkeys:

    • name:

      • Mandatory. Name of the module package.
      • Type: String.
    • state:

      • Optional. State of the module package. Possible options: absent, present.
      • Type: String.
      • Default: 'present'

php__version

  • Debian only. The PHP version this host runs, for example '8.4'. Makes the role install the versioned packages (php8.4-cli, php8.4-fpm, ...) instead of the unversioned metapackages, pin the php alternatives to it, and purge other versions on php:update. Empty adopts whatever version the configured repos provide, which is correct without the sury repo. Has no effect on RedHat, where the module stream pins the version at repo level.
  • Type: String.
  • Default: ''

Example:

# optional
php__fpm_service_enabled: true
php__fpm_service_state: 'started'
php__fpm_pools__host_var:
  - name: 'librenms'
    user: 'librenms'
    group: 'librenms'
    raw: |-
      env[PATH] = /usr/local/bin:/usr/bin:/bin
php__modules__host_var:
  - name: 'php-mysqlnd'
    state: 'present'
php__version: '8.4'

Optional Role Variables - php__ini_* Config Directives

Variables for php.ini directives and their default values, defined and supported by this role.

php__ini_date_timezone__group_var / php__ini_date_timezone__host_var

  • The default timezone used by all date/time functions. php.net
  • Type: String.
  • Default: 'Europe/Zurich'

php__ini_default_socket_timeout__group_var / php__ini_default_socket_timeout__host_var

  • Default timeout in seconds for socket based streams (e.g. HTTP, FTP). php.net
  • Type: Number.
  • Default: 10

php__ini_display_errors__group_var / php__ini_display_errors__host_var

  • This determines whether errors should be printed to the screen as part of the output or if they should be hidden from the user. This is a feature to support your development and should never be used on production systems (e.g. systems connected to the internet). php.net
  • Type: String.
  • Default: 'Off'

php__ini_display_startup_errors__group_var / php__ini_display_startup_errors__host_var

  • Even when display_errors is on, errors that occur during PHP's startup sequence are not displayed. It's strongly recommended to keep this off. php.net
  • Type: String.
  • Default: 'Off'

php__ini_error_reporting__group_var / php__ini_error_reporting__host_var

  • Set the error reporting level. php.net
  • Type: String.
  • Default: 'E_ALL & ~E_NOTICE & ~E_DEPRECATED'

php__ini_max_execution_time__group_var / php__ini_max_execution_time__host_var

  • This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser. This helps prevent poorly written scripts from tying up the server. The default setting is 30. When running PHP from the command line the default setting is 0. php.net
  • The PHP-FPM pools also set it as php_admin_value[max_execution_time], which takes precedence over the z00-linuxfabrik.ini. Set php_admin_value_max_execution_time in php__fpm_pools__*_var for a different value per pool.
  • Type: Number.
  • Default: 30

php__ini_max_file_uploads__group_var / php__ini_max_file_uploads__host_var

  • The maximum number of files allowed to be uploaded simultaneously. php.net
  • Type: Number.
  • Default: 50

php__ini_max_input_time__group_var / php__ini_max_input_time__host_var

  • This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time. php.net
  • Type: Number.
  • Default: -1

php__ini_max_input_vars__group_var / php__ini_max_input_vars__host_var

  • How many input variables may be accepted (limit is applied to $_GET, $_POST and $_COOKIE superglobal separately). Use of this directive mitigates the possibility of denial of service attacks which use hash collisions. If there are more input variables than specified by this directive, an E_WARNING is issued, and further input variables are truncated from the request. php.net
  • The PHP-FPM pools also set it as php_admin_value[max_input_vars], which takes precedence over the z00-linuxfabrik.ini. Set php_admin_value_max_input_vars in php__fpm_pools__*_var for a different value per pool.
  • Type: Number.
  • Default: 1000

php__ini_memory_limit__group_var / php__ini_memory_limit__host_var

  • This sets the maximum amount of memory in bytes that ONE RUNNING SCRIPT is allowed to allocate. This helps prevent poorly written scripts for eating up all available memory on a server. Note that to have no memory limit, set this directive to -1. Again: PHP memory_limit is per-script, just as a highway's speed limit is per-vehicle. php.net
  • The PHP-FPM pools also set it as php_admin_value[memory_limit], which takes precedence over the z00-linuxfabrik.ini. Set php_admin_value_memory_limit in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: '128M'

php__ini_opcache_blacklist_filename__group_var / php__ini_opcache_blacklist_filename__host_var

  • A blacklist file is a text file containing the names of files that should not be accelerated, one per line. Wildcards are allowed, and prefixes can also be provided. Lines starting with a semi-colon are ignored as comments. php.net
  • Type: String.
  • Default: '/etc/opcache.blacklist'

php__ini_opcache_enable__group_var / php__ini_opcache_enable__host_var

  • Enables the opcode cache. When disabled, code is not optimised or cached. php.net
  • Type: Number.
  • Default: 1

php__ini_opcache_enable_cli__group_var / php__ini_opcache_enable_cli__host_var

  • Enables the opcode cache for the CLI version of PHP. php.net
  • Type: Number.
  • Default: 1

php__ini_opcache_huge_code_pages__group_var / php__ini_opcache_huge_code_pages__host_var

  • Enables or disables copying of PHP code (text segment) into HUGE PAGES. This should improve performance, but requires appropriate OS configuration. php.net
  • Type: Number.
  • Default: 0

php__ini_opcache_interned_strings_buffer__group_var / php__ini_opcache_interned_strings_buffer__host_var

  • The amount of memory used to store interned strings, in megabytes. php.net
  • Type: Number.
  • Default: 12

php__ini_opcache_max_accelerated_files__group_var / php__ini_opcache_max_accelerated_files__host_var

  • The maximum number of keys (and therefore scripts) in the OPcache hash table. The actual value used will be the first number in the set of prime numbers { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 } that is greater than or equal to the configured value. php.net
  • Type: Number.
  • Default: 7963

php__ini_opcache_memory_consumption__group_var / php__ini_opcache_memory_consumption__host_var

  • The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set. php.net
  • Type: Number.
  • Default: 128

php__ini_opcache_revalidate_freq__group_var / php__ini_opcache_revalidate_freq__host_var

  • How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request. php.net
  • Type: Number.
  • Default: 60

php__ini_opcache_save_comments__group_var / php__ini_opcache_save_comments__host_var

  • If disabled, all documentation comments will be discarded from the opcode cache to reduce the size of the optimised code. Disabling this configuration directive may break applications and frameworks that rely on comment parsing for annotations. php.net
  • Type: Number.
  • Default: 1

php__ini_opcache_validate_timestamps__group_var / php__ini_opcache_validate_timestamps__host_var

  • If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds. When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate() or by restarting the Web server for changes to the filesystem to take effect. php.net
  • Type: Number.
  • Default: 1

php__ini_post_max_size__group_var / php__ini_post_max_size__host_var

  • Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. php.net
  • The PHP-FPM pools also set it as php_admin_value[post_max_size], which takes precedence over the z00-linuxfabrik.ini. Set php_admin_value_post_max_size in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: '8M'

php__ini_session_cookie_httponly__group_var / php__ini_session_cookie_httponly__host_var

  • Marks the session cookie as HttpOnly, so it is not accessible to JavaScript via document.cookie, mitigating cookie theft via XSS. php.net
  • The PHP-FPM pools also set it as php_value[session.cookie_httponly], which takes precedence over the z00-linuxfabrik.ini. Set php_value_session_cookie_httponly in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: 'On'

php__ini_session_cookie_samesite__group_var / php__ini_session_cookie_samesite__host_var

  • The SameSite attribute of the session cookie, which decides on which cross-site requests the browser sends it. One of Lax, Strict, None or an empty string. PHP only validates the value when a script sets it through ini_set(); read from an ini file it is written into the Set-Cookie header verbatim, so a typo silently ships a nonsense attribute that browsers then ignore. The role's meta/argument_specs.yml therefore restricts the variable to the four accepted values and aborts at role entry instead. php.net
  • Lax sends the cookie on same-site requests and on top-level cross-site navigations, but not on cross-site POSTs, iframes or XHR, which is what stops a foreign page from acting under the visitor's session. Strict withholds it on a top-level navigation as well, so a user following a link from an email lands logged out until the next click. None switches the protection off and only works together with php__ini_session_cookie_secure__*_var, otherwise browsers drop the cookie entirely. An empty string emits no attribute and leaves the decision to the browser, which differs between Chrome, Firefox and Safari.
  • Set None (with Secure) for an application whose identity provider returns through a cross-site POST, as SAML HTTP-POST binding and the OIDC form_post response mode do: with Lax the callback arrives without the session and the login loops. On a host serving more than one application, set it for that pool alone via php_value_session_cookie_samesite in php__fpm_pools__*_var instead of host-wide. Applications embedded from another registrable domain need it too. An identity provider or an embedded service under the same registrable domain, for example Collabora at office.example.com inside Nextcloud at cloud.example.com, counts as same-site and is unaffected.
  • Only deployed from PHP 7.3 on, since PHP 7.2 does not know the directive.
  • The PHP-FPM pools also set it as php_value[session.cookie_samesite], which takes precedence over the z00-linuxfabrik.ini. Set php_value_session_cookie_samesite in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: 'Lax'
  • Deviates from the upstream default, which is an empty string up to PHP 8.5 and therefore emits no attribute at all. PHP itself moves to Lax in 8.6, so this anticipates the upstream default rather than departing from it.

php__ini_session_cookie_secure__group_var / php__ini_session_cookie_secure__host_var

  • Marks the session cookie Secure, so a browser only ever sends it back over HTTPS. php.net
  • Set it to 'Off' for a site genuinely served over plain HTTP, which otherwise cannot log anyone in: the browser accepts the cookie and then never returns it. On a host serving both, set it for the affected pool alone via php_value_session_cookie_secure in php__fpm_pools__*_var.
  • What decides is the scheme the browser uses, not what PHP sees. A site behind a reverse proxy that terminates TLS and forwards plain HTTP is an HTTPS site for this purpose, and is exactly the case where PHP cannot work the flag out for itself.
  • Switching an HTTPS host from 'Off' to 'On' logs nobody out. PHP only sends Set-Cookie when it creates a session ID, so a running session resumes untouched and its existing cookie keeps its old attributes until the application regenerates the ID, usually at the next login, or the session expires.
  • The PHP-FPM pools also set it as php_value[session.cookie_secure], which takes precedence over the z00-linuxfabrik.ini. Set php_value_session_cookie_secure in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: 'On'
  • Deviates from the upstream default Off: without the flag the session ID travels in cleartext on any http:// request to the host, before a redirect to HTTPS can fire, and LFOps deploys these sites behind TLS. PHP sets the flag on its own only when it sees HTTPS itself, which it does not when TLS is terminated in front of it.

php__ini_session_gc_maxlifetime__group_var / php__ini_session_gc_maxlifetime__host_var

  • Number of seconds after which session data is treated as garbage and cleaned up by the session garbage collector. php.net
  • Type: Number.
  • Default: 1440

php__ini_session_sid_length__group_var / php__ini_session_sid_length__host_var

  • Length of the session ID string. Only takes effect on PHP versions that still honor the directive; PHP deprecates any value other than the built-in 32. php.net
  • Type: Number.
  • Default: 32

php__ini_session_trans_sid_tags__group_var / php__ini_session_trans_sid_tags__host_var

  • HTML tags whose attributes are rewritten to include the session ID when transparent SID support is enabled. php.net
  • Type: String.
  • Default: 'a=href,area=href,frame=src,input=src,form=fakeentry'

php__ini_smtp__group_var / php__ini_smtp__host_var

  • Host used by the mail() function to send mail (Windows only; ignored on Unix, where the sendmail_path binary is used). php.net
  • Type: String.
  • Default: 'localhost'

php__ini_upload_max_filesize__group_var / php__ini_upload_max_filesize__host_var

  • The maximum size of an uploaded file. php.net
  • The PHP-FPM pools also set it as php_admin_value[upload_max_filesize], which takes precedence over the z00-linuxfabrik.ini. Set php_admin_value_upload_max_filesize in php__fpm_pools__*_var for a different value per pool.
  • Type: String.
  • Default: '2M'

Note that setting php__ini_opcache_huge_code_pages__group_var or php__ini_opcache_huge_code_pages__host_var to 1 might require enabling the SELinux boolean httpd_execmem on RHEL systems.

Example:

# optional
php__ini_date_timezone__host_var: 'Europe/Zurich'
php__ini_default_socket_timeout__host_var: 10
php__ini_display_errors__host_var: 'Off'
php__ini_display_startup_errors__host_var: 'Off'
php__ini_error_reporting__host_var: 'E_ALL & ~E_NOTICE & ~E_DEPRECATED'
php__ini_max_execution_time__host_var: 3600
php__ini_max_file_uploads__host_var: 100
php__ini_max_input_time__host_var: -1
php__ini_max_input_vars__host_var: 1000
php__ini_memory_limit__host_var: '1024M'
php__ini_opcache_blacklist_filename__host_var: '/etc/opcache.blacklist'
php__ini_opcache_enable__host_var: 1
php__ini_opcache_enable_cli__host_var: 1
php__ini_opcache_huge_code_pages__host_var: 0
php__ini_opcache_interned_strings_buffer__host_var: 12
php__ini_opcache_max_accelerated_files__host_var: 7963
php__ini_opcache_memory_consumption__host_var: 128
php__ini_opcache_revalidate_freq__host_var: 60
php__ini_opcache_save_comments__host_var: 1
php__ini_opcache_validate_timestamps__host_var: 1
php__ini_post_max_size__host_var: '8M'
php__ini_session_cookie_httponly__host_var: 'On'
php__ini_session_cookie_samesite__host_var: 'Lax'
php__ini_session_cookie_secure__host_var: 'On'
php__ini_session_gc_maxlifetime__host_var: 1440
php__ini_session_sid_length__host_var: 32
php__ini_session_trans_sid_tags__host_var: 'a=href,area=href,frame=src,input=src,form=fakeentry'
php__ini_smtp__host_var: 'localhost'
php__ini_upload_max_filesize__host_var: '10000M'

Optional Role Variables - PHP-FPM Global Config Directives

Variables for the [global] section of the PHP-FPM configuration, deployed as z00-linuxfabrik-global.conf next to the pools. Only directives that no packaged php-fpm.conf sets itself can be configured here, see "How the Role Behaves".

php__fpm_conf_emergency_restart_interval__group_var / php__fpm_conf_emergency_restart_interval__host_var

  • The window php__fpm_conf_emergency_restart_threshold__*_var counts within. Available units: s(econds), m(inutes), h(ours), or d(ays). A value of 0 switches the mechanism off.
  • Type: String.
  • Default: '1m'
  • Deviates from the upstream default 0: the mechanism needs a non-zero threshold and a non-zero interval, so leaving either at zero switches it off.

php__fpm_conf_emergency_restart_threshold__group_var / php__fpm_conf_emergency_restart_threshold__host_var

  • Reload PHP-FPM once this many workers have died on SIGSEGV or SIGBUS within php__fpm_conf_emergency_restart_interval__*_var. A value of 0 means off.
  • Type: Number.
  • Default: 10
  • Deviates from the upstream default 0: an extension or opcode cache that corrupts its workers otherwise keeps crashing them until somebody notices, while a reload of the master usually restores service. Ten crashes in a minute is well clear of ordinary application fatals, which do not count here: only SIGSEGV and SIGBUS do. PHP-FPM writes a WARNING when it triggers, so the crash still reaches monitoring instead of being papered over.

php__fpm_conf_log_level__group_var / php__fpm_conf_log_level__host_var

  • The log level of PHP-FPM's own error log. Possible values: alert, error, warning, notice, debug.
  • Type: String.
  • Default: 'notice'
  • Matches the upstream default but is pinned rather than left unset, because PHP-FPM keeps an unset value at zero internally and php-fpm -tt then dumps log_level = unknown value instead of the level actually in effect. Raising it to warning drops the start, reload and shutdown markers that make a pool restarting in a loop visible, and silences the php-fpm -tt configuration dump along with them.

Example:

# optional
php__fpm_conf_emergency_restart_interval__host_var: '1m'
php__fpm_conf_emergency_restart_threshold__host_var: 10
php__fpm_conf_log_level__host_var: 'notice'

Optional Role Variables - PHP-FPM Pool Config Directives

Variables for PHP-FPM pool directives and their default values, defined and supported by this role.

php__fpm_pool_conf_pm__group_var / php__fpm_pool_conf_pm__host_var

  • Choose how the process manager will control the number of child processes.
  • Type: String.
  • Default: 'dynamic'

php__fpm_pool_conf_pm_max_children__group_var / php__fpm_pool_conf_pm_max_children__host_var

  • The number of child processes to be created when pm is set to 'static' and the maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
  • Type: Number.
  • Default: 50
  • Deviates from the upstream default on Debian, which ships 5: the role uses one value for both families, and 5 workers serve a single application on a modern host badly. Note the ceiling this sets on memory: each worker may grow to php__ini_memory_limit__*_var, and the value applies per pool, so a host with several pools multiplies it.

php__fpm_pool_conf_pm_max_spare_servers__group_var / php__fpm_pool_conf_pm_max_spare_servers__host_var

  • The desired maximum number of idle server processes.
  • Type: Number.
  • Default: 35
  • Deviates from the upstream default on Debian, which ships 3: the role uses the RedHat package value for both families. Idle workers are only reaped down to this number, so this is what a pool costs at rest.

php__fpm_pool_conf_pm_min_spare_servers__group_var / php__fpm_pool_conf_pm_min_spare_servers__host_var

  • The desired minimum number of idle server processes.
  • A pool that keeps falling below this logs seems busy once per second until it recovers, see "How the Role Behaves".
  • Type: Number.
  • Default: 5
  • Deviates from the upstream default on Debian, which ships 1: the role uses the RedHat package value for both families.

php__fpm_pool_conf_pm_start_servers__group_var / php__fpm_pool_conf_pm_start_servers__host_var

  • The number of child processes created on startup. Must be greater than php__fpm_pool_conf_pm_min_spare_servers__*_var but less than php__fpm_pool_conf_pm_max_spare_servers__*_var.
  • Type: Number.
  • Default: 5
  • Deviates from the upstream default on Debian, which ships 2: the role uses the RedHat package value for both families.

php__fpm_pool_conf_request_slowlog_timeout__group_var / php__fpm_pool_conf_request_slowlog_timeout__host_var

  • The timeout for serving a single request after which a PHP backtrace will be dumped to the slowlog file. A value of 0 means off. Available units: s(econds, default), m(inutes), h(ours), or d(ays). The slowlog is written to the per-service log directory, /var/log/php-fpm/<pool>-slow.log on RedHat and /var/log/<service>/<pool>-slow.log on Debian, for example /var/log/php8.4-fpm/www-slow.log. On RedHat the backtrace also needs the lfops_php_fpm_slowlog SELinux module, see "How the Role Behaves".
  • Type: Number.
  • Default: 0

php__fpm_pool_conf_request_terminate_timeout__group_var / php__fpm_pool_conf_request_terminate_timeout__host_var

  • The timeout for serving a single request after which the worker process will be killed. This is the backstop for requests that max_execution_time cannot stop, because the script is blocked in a system call (a database query, an outgoing HTTP request) rather than executing PHP. Without it such a worker occupies its slot until it returns on its own, which fills up pm.max_children under load long after the web server or a reverse proxy in front of it gave up on the request. Keep it above php__ini_max_execution_time__*_var (default 30), so a script still hits PHP's own limit first and gets a proper error and log entry, and above the web server's own timeout (apache_httpd__conf_timeout, default 10). A value of 0 means off. Available units: s(econds, default), m(inutes), h(ours), or d(ays).
  • Type: String.
  • Default: '60s'
  • Deviates from the upstream default 0 (off): without it a worker blocked in a system call occupies its slot until it returns on its own, long after the web server or a reverse proxy gave up on the request, which is how pm.max_children fills up under load.

php__fpm_pools__host_var / php__fpm_pools__group_var

  • List of dictionaries containing PHP-FPM pools.
  • For the usage in host_vars / group_vars (can only be used in one group at a time).
  • Type: List of dictionaries.
  • Default: []
  • Subkeys:

    • name:

      • Mandatory. The name of the pool. Will also be used as the filename and for logfiles.
      • Type: String.
    • state:

      • Optional. State of the pool. Possible options: absent, present.
      • Type: String.
      • Default: 'present'
    • user:

      • Optional. The Unix user running the pool processes. php.net
      • Type: String.
      • Default: 'apache' (RedHat), 'www-data' (Debian)
    • group:

      • Optional. The Unix group running the pool processes. php.net
      • Type: String.
      • Default: 'apache' (RedHat), 'www-data' (Debian)
    • listen_acl_users:

      • Optional. The users granted access to the pool's socket, as a POSIX ACL. php.net
      • Type: List of strings.
      • Default: the web server user, apache (RedHat) or www-data (Debian)
      • Deviates from the upstream default on both families: the RedHat package grants apache,nginx, and the Debian package hands the socket over through listen.owner / listen.group instead. The role grants only Apache HTTPd, since that is the web server currently provided by LFOps, and grants it by ACL on both, so socket access does not depend on who the pool runs as.
    • pm:

      • Optional. Choose how the process manager will control the number of child processes. php.net
      • Type: String.
      • Default: {{ php__fpm_pool_conf_pm__combined_var }} (which defaults to 'dynamic')
    • pm_max_children:

      • Optional. The number of child processes to be created when pm is set to 'static', and the maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. php.net
      • Type: Number.
      • Default: {{ php__fpm_pool_conf_pm_max_children__combined_var }} (which defaults to 50)
    • pm_start_servers:

      • Optional. The number of child processes created on startup. Must be greater than pm_min_spare_servers but less than pm_max_spare_servers. Used only when pm is set to 'dynamic'. php.net
      • Type: Number.
      • Default: {{ php__fpm_pool_conf_pm_start_servers__combined_var }} (which defaults to 5)
    • pm_min_spare_servers:

      • Optional. The desired minimum number of idle server processes. Used only when pm is set to 'dynamic'. php.net
      • Type: Number.
      • Default: {{ php__fpm_pool_conf_pm_min_spare_servers__combined_var }} (which defaults to 5)
    • pm_max_spare_servers:

      • Optional. The desired maximum number of idle server processes. Used only when pm is set to 'dynamic'. Idle workers are only reaped down to this number, so on a host with several pools this is what they cost at rest. php.net
      • Type: Number.
      • Default: {{ php__fpm_pool_conf_pm_max_spare_servers__combined_var }} (which defaults to 35)
    • pm_max_spawn_rate:

      • Optional. The number of child processes to spawn at once. Used only when pm is set to 'dynamic'. Only rendered on PHP 8.1 and newer, where the directive exists. php.net
      • Type: Number.
      • Default: 32
    • pm_process_idle_timeout:

      • Optional. The number of seconds after which an idle process will be killed. Used only when pm is set to 'ondemand'. Available units: s(econds, default), m(inutes), h(ours), or d(ays). php.net
      • Type: String.
      • Default: '10s'
    • pm_max_requests:

      • Optional. The number of requests each child process should execute before respawning, which bounds the damage a leaking third-party library can do. For endless request processing specify 0. php.net
      • Type: Number.
      • Default: 500
      • Deviates from the upstream default 0 (never respawn): a worker that leaks memory keeps it for the lifetime of the service, so a slow leak in an application or an extension eventually shows up as an out-of-memory kill rather than as a recycled worker.
    • pm_status_path:

      • Optional. Path to view the FPM status page. Set to an empty string to disable the status page for this pool. php.net
      • Type: String.
      • Default: '/fpm-status'
      • Deviates from the upstream default, which sets no status path at all and therefore serves no status page: the php-fpm-status check of the Linuxfabrik Monitoring Plugins reads it, and defaults to exactly this path.
    • ping_path:

      • Optional. The ping path to check if FPM is alive and responding. Set to an empty string to disable the ping endpoint for this pool. php.net
      • Type: String.
      • Default: '/fpm-ping'
      • Deviates from the upstream default, which sets no ping path at all: the php-fpm-ping check of the Linuxfabrik Monitoring Plugins reads it, and defaults to exactly this path.
    • request_slowlog_timeout:

      • Optional. The timeout for serving a single request after which a PHP backtrace will be dumped to the slowlog file. A value of 0 means off. Available units: s(econds, default), m(inutes), h(ours), or d(ays). php.net
      • Type: Number.
      • Default: {{ php__fpm_pool_conf_request_slowlog_timeout__combined_var }} (which defaults to 0)
    • request_slowlog_trace_depth:

      • Optional. Depth of the slowlog stack trace. php.net
      • Type: Number.
      • Default: 20
    • request_terminate_timeout:

      • Optional. The timeout for serving a single request after which the worker process will be killed. A value of 0 means off. Available units: s(econds, default), m(inutes), h(ours), or d(ays). php.net
      • Type: String.
      • Default: {{ php__fpm_pool_conf_request_terminate_timeout__combined_var }} (which defaults to '60s')
    • php_admin_value_max_execution_time:

      • Optional. Enforced as php_admin_value, so an application cannot raise it at runtime via ini_set(). php.net
      • Type: Number.
      • Default: {{ php__ini_max_execution_time__combined_var }}
      • Deviates from the upstream default in enforcement, not in value: upstream sets it in php.ini only, where an application can raise it at runtime with ini_set(). As a php_admin_value it is a ceiling the pool cannot exceed, which is what keeps one pool from taking the host down for the others. The CLI is unaffected, so cron jobs and maintenance scripts keep the php.ini value and their own ini_set().
    • php_admin_value_max_input_vars:

      • Optional. Enforced as php_admin_value. php.net
      • Type: Number.
      • Default: {{ php__ini_max_input_vars__combined_var }}
      • Deviates from the upstream default in enforcement, not in value: upstream sets it in php.ini only, where an application can raise it at runtime with ini_set(). As a php_admin_value it is a ceiling the pool cannot exceed, which is what keeps one pool from taking the host down for the others. The CLI is unaffected, so cron jobs and maintenance scripts keep the php.ini value and their own ini_set().
    • php_admin_value_memory_limit:

      • Optional. Enforced as php_admin_value. php.net
      • Type: String.
      • Default: '{{ php__ini_memory_limit__combined_var }}'
      • Deviates from the upstream default in enforcement, not in value: upstream sets it in php.ini only, where an application can raise it at runtime with ini_set(). As a php_admin_value it is a ceiling the pool cannot exceed, which is what keeps one pool from taking the host down for the others. The CLI is unaffected, so cron jobs and maintenance scripts keep the php.ini value and their own ini_set().
    • php_admin_value_open_basedir:

      • Optional. Limits the files the pool may access to the given paths. php.net
      • Type: String.
      • Default: unset
    • php_admin_value_post_max_size:

      • Optional. Enforced as php_admin_value. php.net
      • Type: String.
      • Default: '{{ php__ini_post_max_size__combined_var }}'
      • Deviates from the upstream default in enforcement, not in value: upstream sets it in php.ini only, where an application can raise it at runtime with ini_set(). As a php_admin_value it is a ceiling the pool cannot exceed, which is what keeps one pool from taking the host down for the others. The CLI is unaffected, so cron jobs and maintenance scripts keep the php.ini value and their own ini_set().
    • php_admin_value_session_save_handler:

      • Optional. The session storage backend for this pool. Enforced as php_admin_value, so an application cannot switch it at runtime via ini_set(). Set it to redis or memcached (with the matching php_admin_value_session_save_path connection string, and the extension installed via php__modules__*_var) on a host whose sessions have to survive beyond one machine, for example behind a load balancer. The role then creates no session directory for the pool. php.net
      • Type: String.
      • Default: 'files'
      • Deviates from the upstream default in enforcement, not in value: the RedHat package sets it in the pool as php_value, which ini_set() can still change, and the Debian package leaves it to php.ini. As a php_admin_value an application cannot switch its session backend at runtime, which is what keeps a pool's sessions in the directory the role created for it. The CLI is unaffected.
    • php_admin_value_session_save_path:

      • Optional. With the default files handler the role creates this directory, owned by the pool's user / group with mode 0700. On RedHat it inherits the httpd_var_run_t SELinux type from the session base; pointing it outside that base means labeling it yourself. With another php_admin_value_session_save_handler this is the backend's connection string (for example tcp://192.0.2.10:6379) and no directory is created. php.net
      • Type: String.
      • Default: /var/lib/php/session/<pool> (RedHat), /var/lib/php/sessions/<pool> (Debian)
      • Deviates from the upstream default, which points every pool at the one shared session base (/var/lib/php/session on RedHat as a php_value, /var/lib/php/sessions from php.ini on Debian): pools sharing one directory can read each other's session files, and with it each other's logged-in users.
    • php_value_session_cookie_httponly / php_value_session_cookie_samesite / php_value_session_cookie_secure:

      • Optional. The session cookie policy for this pool, overriding the host-wide php__ini_session_cookie_*__*_var for its own workers. Use them where one host serves applications with different needs: an application whose identity provider returns through a cross-site POST (SAML HTTP-POST binding, OIDC form_post) needs php_value_session_cookie_samesite: 'None' together with php_value_session_cookie_secure: 'On', which browsers require for None, while the rest of the host keeps Lax. An internal site served over plain HTTP needs php_value_session_cookie_secure: 'Off'. session.cookie_samesite is only rendered from PHP 7.3 on, since 7.2 does not know the directive.
      • Type: String.
      • Default: the host-wide php__ini_session_cookie_httponly__*_var, php__ini_session_cookie_samesite__*_var and php__ini_session_cookie_secure__*_var
      • Deployed as php_value and not as php_admin_value, unlike the limits above: those are ceilings a pool must not raise, whereas the cookie policy is something an application may legitimately manage itself through session_set_cookie_params(). As a php_admin_value that call is refused (ini_set() returns false and the value does not move), and an application that deliberately needs None fails at runtime as a login loop rather than visibly. php_value also keeps the semantics the php.ini setting already had.
    • php_admin_value_soap_wsdl_cache_dir:

      • Optional. Where the SOAP extension caches parsed WSDL files. The role creates this directory, owned by the pool's user / group with mode 0700. php.net
      • Type: String.
      • Default: /var/lib/php/wsdlcache/<pool>
      • Deviates from the upstream default /tmp, which the RedHat package already overrides with the shared /var/lib/php/wsdlcache: a cached WSDL carries the internal endpoints and message types of the service it describes, and pools do not necessarily run as the same user, so each pool caches into its own directory.
    • php_admin_value_upload_max_filesize:

      • Optional. Enforced as php_admin_value. php.net
      • Type: String.
      • Default: '{{ php__ini_upload_max_filesize__combined_var }}'
      • Deviates from the upstream default in enforcement, not in value: upstream sets it in php.ini only, where an application can raise it at runtime with ini_set(). As a php_admin_value it is a ceiling the pool cannot exceed, which is what keeps one pool from taking the host down for the others. The CLI is unaffected, so cron jobs and maintenance scripts keep the php.ini value and their own ini_set().
    • raw:

      • Optional. Raw content which will be added to the end of the pool config.
      • Type: String.
      • Default: unset

Example:

# optional
php__fpm_pool_conf_pm__host_var: 'dynamic'
php__fpm_pool_conf_pm_max_children__host_var: 50
php__fpm_pool_conf_pm_max_spare_servers__host_var: 35
php__fpm_pool_conf_pm_min_spare_servers__host_var: 5
php__fpm_pool_conf_pm_start_servers__host_var: 5
php__fpm_pool_conf_request_slowlog_timeout__host_var: '10s'
php__fpm_pool_conf_request_terminate_timeout__host_var: '60s'
php__fpm_pools__host_var:
  - name: 'librenms'
    user: 'librenms'
    group: 'librenms'
    pm: 'ondemand'
    pm_max_children: 10
    pm_process_idle_timeout: '60s'
    php_admin_value_memory_limit: '256M'
    php_admin_value_open_basedir: '/opt/librenms:/tmp'
    request_terminate_timeout: '120s'
    raw: |-
      env[PATH] = /usr/local/bin:/usr/bin:/bin

Troubleshooting

The run aborts with request_terminate_timeout ... has to be greater than max_execution_time

  • A pool would be configured so that PHP-FPM kills the worker before PHP reaches its own limit, which caps the longer runtime silently and replaces PHP's fatal error with a bare execution timed out. Raise php__fpm_pool_conf_request_terminate_timeout__*_var above php__ini_max_execution_time__*_var (leaving headroom, so PHP's limit is the one that trips), set it per pool via the pool's request_terminate_timeout, or lower the execution time. Setting either of the two to 0 switches that limit off and is accepted.

The run aborts with PHP X.Y is not supported by this role

  • The enabled repositories offer a PHP version this role ships no ini template and vars file for. Either pin the host to a supported version via php__version, or add the matching roles/php/templates/etc/php.d/<version>-z00-linuxfabrik.ini.j2 and roles/php/vars/<version>.yml, and list the version in roles/php/vars/main.yml.

License

The Unlicense

Author Information

Linuxfabrik GmbH, Zurich