Disabling selected PHP functions limits what application code can do when a site, pool, or tenant never needs shell access or child-process execution. Blocking internal helpers such as exec(), shell_exec(), system(), passthru(), proc_open(), and popen() reduces the damage a compromised plugin, uploaded script, or exposed admin tool can do after reaching the runtime.

The control point is the disable_functions directive, a comma-delimited list that PHP resolves when the runtime starts. Current PHP documentation marks it as INI_SYSTEM, so the effective value is controlled at startup rather than by application code or per-directory .user.ini overrides, and current PHP 8 releases remove disabled internal functions from the function table instead of leaving them callable with the older disabled warning.

This is a hardening control rather than a sandbox, so keep the list narrow and confirm the served runtime instead of trusting CLI alone. The steps below stay on the packaged Ubuntu or Debian PHP-FPM layout under /etc/php/<version>/fpm/, where pool files can append extra disabled functions with php_value[disable_functions] or php_admin_value[disable_functions] and broad blocks such as mail, putenv, or proc_open can break queue workers, deployment hooks, or integrations that legitimately shell out.

Steps to disable PHP functions in PHP-FPM:

  1. Check which base PHP-FPM configuration file the selected runtime loads and read the current disable_functions value.
    $ sudo php-fpm8.3 -y /etc/php/8.3/fpm/php-fpm.conf -i | grep -E '^Loaded Configuration File =>|^disable_functions'
    Loaded Configuration File => /etc/php/8.3/fpm/php.ini
    disable_functions => no value => no value

    Replace 8.3 with the installed PHP major.minor version when the host packages another branch. On hosts that ship an unversioned binary, use sudo php-fpm -y /etc/php/<version>/fpm/php-fpm.conf -i instead. This confirms the base php.ini tree for that PHP-FPM binary; pool-level appends still need the next-step pool review or a short-lived probe through the same request path. php --ini reports the CLI tree, which can differ from the web-facing PHP-FPM runtime.

  2. Search the PHP-FPM pool directory for pool-level disable_functions appends before editing the main file.
    $ sudo grep -REn 'php_(admin_)?value\[disable_functions\]' /etc/php/8.3/fpm/pool.d /etc/php-fpm.d 2>/dev/null
    /etc/php/8.3/fpm/pool.d/customer-portal.conf:23:php_admin_value[disable_functions] = show_source

    No output means the sampled pool files are not currently adding extra disabled functions. In PHP-FPM, php_value[disable_functions] and php_admin_value[disable_functions] append to the base php.ini list instead of replacing it, so a served pool can end up with a broader effective list than the base value shown in the first step.

  3. Create a backup of the active PHP-FPM configuration file before editing it.
    $ sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-$(date +%Y%m%d%H%M%S)

    A malformed php.ini can stop new worker processes from loading cleanly, so keep the timestamped backup until the new list is confirmed.

  4. Open the active PHP-FPM configuration file in a text editor.
    $ sudoedit /etc/php/8.3/fpm/php.ini
  5. Set disable_functions to the internal functions that should be unavailable to the runtime.
    disable_functions = exec,shell_exec,system,passthru,proc_open,popen

    Extend an existing list instead of replacing it blindly when the file already disables other functions. Only internal functions are affected by this directive.

    Keep the list tight. Functions such as putenv, mail, or proc_open are common breakpoints for frameworks, job runners, deployment tooling, and integrations that shell out to other programs.

  6. Test the same PHP-FPM configuration tree before reloading the service.
    $ sudo php-fpm8.3 -y /etc/php/8.3/fpm/php-fpm.conf -t
    [26-Mar-2026 09:15:12] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful

    Use the matching binary for the installed package, such as php-fpm on hosts that do not ship a versioned PHP-FPM command.

    Do not reload the service until the configuration test succeeds.

  7. Reload the PHP-FPM service so new worker processes pick up the updated directive.
    $ sudo systemctl reload php8.3-fpm

    Packaged Ubuntu and Debian systems commonly use a versioned unit such as php8.3-fpm, while other layouts can expose an unversioned unit such as php-fpm. Reload Apache instead when the application runs through the Apache module instead of PHP-FPM.

  8. Verify that the base PHP-FPM startup configuration now reports the disabled list.
    $ sudo php-fpm8.3 -y /etc/php/8.3/fpm/php-fpm.conf -i | grep -E '^Loaded Configuration File =>|^disable_functions'
    Loaded Configuration File => /etc/php/8.3/fpm/php.ini
    disable_functions => exec,shell_exec,system,passthru,proc_open,popen => exec,shell_exec,system,passthru,proc_open,popen

    This confirms the startup value from the base php.ini tree for the chosen PHP-FPM binary. When the application is served through a pool, virtual host, or different SAPI that can append or diverge from that base value, confirm the same request path with a short-lived probe instead of trusting CLI output alone.