The single highest-impact PHP hardening measure: disabling shell-execution functions. When malware like webshells gets uploaded to a compromised site, they rely on these functions to execute system commands — downloading additional payloads, exfiltrating data, escalating to other sites on the server. Disabling 7 specific functions breaks most webshells while keeping every legitimate framework working. This guide explains the list, the reasoning, and exactly how to apply it.
The list of 7
disable_functions = system,exec,passthru,shell_exec,popen,pcntl_exec,pcntl_fork
Each function explained:
- system() — Runs a shell command and prints output.
- exec() — Runs a shell command, returns last line of output.
- passthru() — Like exec but passes raw binary output.
- shell_exec() — Backtick equivalent; runs command and returns all output as string.
- popen() — Opens a process and returns a pipe to its I/O.
- pcntl_exec() — Replaces current PHP process with another executable.
- pcntl_fork() — Forks a new process at OS level.
Every webshell on the internet uses one or more of these. Disable them and most attack toolkits stop working immediately.
Why proc_open is NOT on the list
proc_open() is also a process-execution function — it could theoretically be exploited the same way. But it’s used legitimately by many critical tools:
- Composer uses proc_open for installing PHP packages.
- Laravel Artisan uses it for many of its CLI commands.
- Symfony Process component is built on proc_open.
- PHPUnit uses it for test execution.
- WP-CLI uses it for various operations.
- Many CI/CD pipelines rely on it.
Disabling proc_open breaks all of these. Some hardening guides recommend it; in practice the cost is too high for most sites.
The 7-function list is the sweet spot: blocks real attack surface, keeps legitimate tooling working. Adding proc_open creates more problems than it solves.
Applying the hardening
Step 1: Check current state
Open cPanel → Software → MultiPHP INI Editor. Select the PHP version your domains use (check via MultiPHP Manager first). Switch to Editor Mode.
Search (Ctrl+F) for disable_functions. You’ll see one of:
- Commented out:
;disable_functions =— no functions disabled. - Already set with some functions — you’ll be extending the list.
- Not present at all — you’ll add it.
Step 2: Apply the list
Replace or add the directive:
disable_functions = system,exec,passthru,shell_exec,popen,pcntl_exec,pcntl_fork
No quotes, no spaces around commas (some PHP versions are picky). Save changes.
Step 3: Repeat for every PHP version in use
MultiPHP INI Editor edits per-version settings. If your account has domains using ea-php82 and ea-php83, apply the change to both versions. The version dropdown at the top of the page switches between them.
Step 4: Verify
Create info.php with phpinfo(), visit it. Search (Ctrl+F) for “disable_functions”. Should show all 7 functions in the value column.
Delete info.php after.
What breaks (and how to verify nothing breaks)
Most well-written applications don’t call any of the 7 disabled functions. Specific cases that DO call them:
- Some shared hosting tools (file managers, backup scripts) that wrap shell commands.
- Old WordPress security plugins that scan disk by spawning shells (modern ones use PHP-native methods).
- Custom scripts you’ve written that called exec() directly.
- Some image processors that wrapped ImageMagick CLI (proper imagick extension is safer).
For WordPress: nothing in core, Gutenberg, common themes, WooCommerce, Elementor, Yoast, or major plugins requires these functions. They use PHP-native methods.
If a plugin breaks after hardening, the plugin is using outdated patterns — find an alternative or contact its developer. Don’t undo the hardening to keep a poorly written plugin working.
Testing your site after applying
- Load home page — should work fine.
- Log into WordPress admin — verify dashboards load.
- Test a backup operation if you have a backup plugin.
- Test an image upload and resize.
- Test sending a transactional email.
- Check error logs for new errors related to disabled functions.
If you see PHP fatal errors like “Call to undefined function shell_exec()” — the function is being called somewhere. Identify which plugin/theme; consider whether you actually need it.
Going further (advanced, optional)
The 7-function list is the minimum effective hardening. Additional functions sometimes added by security guides:
- show_source / highlight_file — Show PHP source. Used by some debugging tools. Disabling is low risk.
- eval — Cannot be disabled via disable_functions (it’s a language construct, not a function). Disabling requires a special build of PHP.
- assert — Used by some webshells via eval-like pattern. Default uses are mostly debugging; disabling rare.
For most sites, stop at the original 7. Adding more rarely adds proportional protection but does increase the chance of breaking something.
If you can’t edit MultiPHP INI Editor
Some hosting plans don’t expose the INI Editor to end users. In that case:
- Open a ticket asking us to apply disable_functions hardening to your account.
- Specify which PHP versions your domains use.
- Provide the 7-function list (or just reference this article).
We can apply server-side. Takes minutes.
Common questions
“Will this stop ALL hacking?” No — but it stops a large class of post-exploitation. Attackers can still steal data through SQL or file reads; they just can’t easily run arbitrary commands. Combine with strong passwords, updates, and access controls.
“What about Imunify360 / CSF / mod_security?” Those are separate layers. disable_functions is intrinsic PHP-level prevention; the others are detection and request filtering. Use them all together for defense in depth.
“Will WordPress / WooCommerce / Laravel work?” Yes. None of them require the 7 disabled functions for normal operation.
“What if a function is needed by something genuinely legitimate?” Re-evaluate: the application is using deprecated patterns. Better to find an alternative than to weaken security baseline. As a last resort, you can set per-domain disable_functions with a shorter list via .user.ini for that specific domain only.
“How can I check if a webshell IS bypassing this?” Imunify360’s malware scanner detects most webshells statically. Cleanup guide. Hardening makes the webshell ineffective if successfully uploaded; scanning catches it being uploaded in the first place.
What’s next
- If compromised already: Compromise response.
- Where to apply this: MultiPHP INI Editor.
- Other PHP settings to tune: Common values.
Five minutes of work, dramatic security improvement. Apply the 7-function disable_functions list across every PHP version your account uses, test that nothing legitimate broke, and you’ve removed one of the largest classes of post-compromise damage. There’s almost no other security measure with this favorable cost/benefit ratio.
Was this helpful?
Thanks for your feedback!