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.
$ 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.
Related: How to find PHP configuration files
$ 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.
$ 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.
$ sudoedit /etc/php/8.3/fpm/php.ini
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.
$ 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.
$ 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.
$ 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.
Related: How to show disabled PHP functions