Skip to content

proc_open / Composer Troubleshooting (cPanel + CloudLinux + Imunify360)

Symptom:

In Process.php line 165:
  The Process class relies on proc_open, which is not available on your PHP installation.

1. Quick test

php -r 'var_dump(function_exists("proc_open"));'

2. Identify the exact runtime

php -r 'var_dump(PHP_BINARY, php_ini_loaded_file(), get_cfg_var("disable_functions"), function_exists("proc_open"));'
php --ini

Run from the project directory and as the account user — cPanel's /usr/local/bin/php wrapper picks the PHP version from the MultiPHP setting of the domain matching the cwd. A .cagefs suffix in PHP_BINARY is normal under CloudLinux CageFS.

Composer's own runtime:

composer exec -- php -r 'var_dump(PHP_BINARY, function_exists("proc_open"));'

3. ini vs. extension

php -n -r 'var_dump(function_exists("proc_open"));'   # -n = ignore all ini files
  • true → an ini file is disabling it (continue to step 4)
  • false → extension / compiled-in

4. Find the ini that wins

grep -rn 'disable_functions' /opt/cpanel/ea-php8*/root/etc/php.d/
ls -la ./php.ini ./.user.ini            # CLI reads php.ini from the cwd first

Only ; is a valid comment char in php.ini — # is not.

5. Definitive: strace the ini load order

strace -f -tt -o /tmp/composer_strace.log composer update
grep -n '\.ini"' /tmp/composer_strace.log | grep -v ENOENT

The last .ini opened wins.

6. Imunify360 hardening (the actual cause here)

/opt/cpanel/ea-phpXX/root/etc/php.d/i360.ini loads the i360 extension, which then reads:

cat /usr/share/i360-php-opts/module.ini

This file is parsed after all normal ini files and overrides them. It is generated by Imunify360 Proactive Defense — do not hand-edit, it gets regenerated.

imunify360-agent config show | grep -iA5 PROACTIVE      # key is PROACTIVE_DEFENCE (British spelling)
imunify360-agent config update '{"PROACTIVE_DEFENCE": {"php_immunity": false}}'
php -r 'var_dump(function_exists("proc_open"));'

Re-enable after the deploy:

imunify360-agent config update '{"PROACTIVE_DEFENCE": {"php_immunity": true}}'

7. Workarounds

php -d disable_functions= /usr/local/bin/composer update -o     # ini-based blocks only
composer update --no-scripts && composer dump-autoload -o --no-scripts
/opt/cpanel/ea-php83/root/usr/bin/php /usr/local/bin/composer update --dry-run --no-scripts

/usr/local/bin/composer uses #!/usr/bin/env php, so it runs under the system default PHP — pin the version explicitly by calling the binary directly.


Test composer.json (PHP 8+)

{
    "name": "test/php8-composer-test",
    "type": "project",
    "require": {
        "php": ">=8.0",
        "monolog/monolog": "^3.0",
        "guzzlehttp/guzzle": "^7.8",
        "symfony/console": "^6.4",
        "vlucas/phpdotenv": "^5.6",
        "ramsey/uuid": "^4.7"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.5"
    },
    "autoload": {
        "psr-4": { "App\\": "src/" }
    },
    "config": {
        "optimize-autoloader": true,
        "sort-packages": true
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}
composer validate --strict && composer show --platform | grep -i '^php '

Side note

Class App\View\Components\confirmModal located in ./app/View/Components/confirm-modal.php
does not comply with psr-4 autoloading standard

Non-fatal. PSR-4 requires the filename to match the class name → rename to confirmModal.php.