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:
- 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.
Related: How to find PHP configuration files
- 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.
- 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.
- Open the active PHP-FPM configuration file in a text editor.
$ sudoedit /etc/php/8.3/fpm/php.ini
- 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.
- 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.
- 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.
- 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.
Related: How to show disabled PHP functions
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
