In a high-density web hosting environment, the modern developer’s toolkit—Node.js, NPM, Composer, and Redis—is no longer an optional “extra” but a baseline requirement for deploying performant PHP and JavaScript applications. However, on a cPanel infrastructure hardened with CloudLinux and Virtual Environments (LVE), providing these tools globally presents a unique challenge: balancing developer flexibility with the strict isolation of CageFS.
This article outlines the professional approach to orchestrating a global development stack. We will move beyond basic installations to focus on the systematic configuration of symlinks, the mapping of CageFS paths, and the critical security layers required to prevent “noisy neighbor” syndrome in a multi-tenant ecosystem.
Core Components of the Stack
- Node.js & NPM: Providing both EA-Nodejs (cPanel native) and Alt-Nodejs (CloudLinux Selector) versions to cater to different application requirements.
- Composer: The industry standard for PHP dependency management, optimized for global execution.
- Redis: Leveraging a persistent, in-memory data structure store to drastically reduce database load and improve application response times.
- CageFS Integration: Ensuring that while these tools are “global,” they remain securely encapsulated within each user’s private environment.
Why Global Access Matters
Without global configuration, users are often forced into manual path exports or local installations that consume unnecessary disk space and create support overhead. By centralizing these binaries, you ensure:
- Standardization: Every user operates on the same vetted versions.
- Simplicity: Commands like
node -vorcomposer install“just work” out of the box. - Performance: Proper CageFS mapping ensures no latency penalty when entering the virtualized environment.
Context 1: When tools are NOT available globally
Individual Composer
[Support Note] Composer was removed in cPanel 130. Users can still use Composer by downloading the PHP archive and running it from the desired path.
To run Composer, you can use the following commands on your cPanel’s terminal:
- Select the directory where you want to install packages, for example: cd public_html/
- Download Composer: wget https://getcomposer.org/composer.phar
- Install dependencies: php composer.phar install
P.S.: The PHP: Phar extension needs to also be enabled on cPanel > Select PHP Version.
Composer is the dependency manager for PHP that allows users to declare, install, and update PHP libraries. It works like a package manager similar to pip for Python or npm for Node.
The user can define his packages in: composer.json file which is located in his web project. Or he can install them individually when needed. For example a user may need phpmailer: php composer.phar require phpmailer/phpmailer
Individual NPM/Node
In this case, the use of nvm is an option, but note that npm install or build requires high resources in RAM.
Context 2: Sysadmin Recommendation: Generalize Composer + Node.js/NPM Globally (Recommended for Shared Hosting)
My old recommendation for PHP apps that need to compile JS libraries, was to create a disabled Node.js app in cPanel just to get its virtual environment e.g.: source /home/cpuser/nodevenv/mynodeapp/22/bin/activate”
My new approach: Many years ago several hosting companies made Node.js + npm available globally for cPanel PHP hosting.
Why we do it globally now:
- Users can run npm install or npm run build directly in any terminal (perfect for Laravel/Vite, Tailwind, etc.).
- No need to create a dummy/disabled Node.js app.
- No conflict with CLN virtual environments — the activate command still overrides the global version when needed.
- We put everything in /usr/local/bin (already in PATH for every user).
- Prefer CloudLinux alt-nodejs24 (modern) instead of old EA-Node.js v16 in /usr/bin.
1. Global Composer (one-time sysadmin setup)
wget https://getcomposer.org/composer.phar -O /usr/local/bin/composerchmod +x /usr/local/bin/composercat > /etc/cagefs/conf.d/composer.cfg <<EOF[composer]comment=Composer global accesspaths=/usr/local/bin/composerEOF2. Global Node.js + NPM (alt-nodejs24)
# Symlinks (auto in PATH)</em>ln -sf /opt/alt/alt-nodejs24/root/usr/bin/node /usr/local/bin/nodeln -sf /opt/alt/alt-nodejs24/root/usr/bin/npm /usr/local/bin/npmln -sf /opt/alt/alt-nodejs24/root/usr/bin/npx /usr/local/bin/npxcat > /etc/cagefs/conf.d/nodejs.cfg <<EOF[alt-nodejs24]comment=CloudLinux Node.js 24 global accesspaths=/opt/alt/alt-nodejs24/root/usr/bin/node, /opt/alt/alt-nodejs24/root/usr/bin/npm, /opt/alt/alt-nodejs24/root/usr/bin/npx, /opt/alt/alt-nodejs24/root/usr/lib/node_modules, /usr/local/bin/node, /usr/local/bin/npm, /usr/local/bin/npxEOF(If you prefer EA-Node.js instead, just change the symlinks to /usr/bin/ (as ea-node is installed in /usr/bin/node and so on is npm, npx) and adjust the .cfg file name/paths.)
Note: /usr/lib/node_modules is required for npm internal components/libs. As for npx is optional but included.
3. Finalize (run once)
cagefsctl --force-updateUsers will now see composer, node, npm, npx directly in their terminal.
This doesn’t conflict with Cloudlinux virtual environment , it will still work through it’s new path. e.g.:
source /home/cpuser/nodevenv/mynodeapp/22/bin/activate && cd /home/cpuser/mynodeappnode -bAutomation: AnsibleScript (for Sysadmins)
Run this with Ansible. first lists ALL available Node.js versions (EA + every alt-nodejs*), choose which one to enable (install if needed) and set Composer + updates CageFS automatically.
node_and_cagefs.md
### README.md# Node.js & Composer Deployment AnsibleUtility to globally enable **Node.js** (EA-Node or CloudLinux Alt) and **Composer** inside CageFS across the cluster.---### OverviewThis repository provides a small Ansible-driven workflow and an example script to expose Node.js (ea-node or alt-node) and Composer binaries and libraries to users inside CageFS. The approach places symlinks and CageFS configuration entries under `/usr/local/bin` and the CloudLinux alt-node paths so they are available to jailed users.---### Prerequisites- Ansible control host with access to target cPanel servers.- Target servers running CageFS (CloudLinux) and cPanel.- Desired Node.js packages installed via `yum` (ea-nodejs or alt-nodejs) or available on the host.- Root or sudo access on target servers.---### 1 Discovery Check Installed VersionsRun this command to detect installed Node.js variants on the target server:```bashansible all -m shell -a '/usr/bin/node -v 2>/dev/null || echo "EA: Not installed"; ls /opt/alt/ 2>/dev/null | grep nodejs || echo "Alt: None found"'```---### 2 Installation If MissingInstall the desired Node.js package on the target server via yum.**EA Node.js example (v22):**```bashansible cpanel_servers -m shell -a "yum install -y ea-nodejs22"```**Alt Node.js example (v24):**```bashansible cpanel_servers -m shell -a "ansible all -m shell -a "yum install -y alt-nodejs24 alt-nodejs24-nodejs alt-nodejs24-npm alt-nodejs24-libicu alt-nodejs24-libicu-devel"```These are the relevant packages I noticed the Node selector currently uses; alternatively, you can install all `alt-nodejsXX*` packages, as shown in the CloudLinux documentation: https://cloudlinux.zendesk.com/hc/en-us/articles/4941095844252-How-to-add-an-ALT-NodeJS-version---### 3 Deploy Script UsageUse the Ansible `script` module to run the deployment script that creates symlinks and writes CageFS config files.**Option A CloudLinux Alt-Node**```bashansible cpanel_servers -m script -a "deploy_node_and_composer.sh alt-nodejs24 composer"```**Option B EA-Node**```bashansible cpanel_servers -m script -a "deploy_node_and_composer.sh ea-node composer"```---### Example CageFS Configs and Composer installPlace the following example CageFS config for Node.js under `/etc/cagefs/conf.d/nodejs.cfg`:```ini[alt-nodejs24]comment=CloudLinux Node.js 24 global accesspaths=/opt/alt/alt-nodejs24/root/usr/bin/node, /opt/alt/alt-nodejs24/root/usr/bin/npm, /opt/alt/alt-nodejs24/root/usr/bin/npx, /opt/alt/alt-nodejs24/root/usr/lib/node_modules, /usr/local/bin/node, /usr/local/bin/npm, /usr/local/bin/npx```Install Composer and expose it globally:```bashwget https://getcomposer.org/composer.phar -O /tmp/composer.pharchmod +x /tmp/composer.pharmv /tmp/composer.phar /usr/local/bin/composer```Create CageFS config for Composer at `/etc/cagefs/conf.d/composer.cfg`:```ini[composer]comment=Composer global accesspaths=/usr/local/bin/composer```After updating configs, refresh CageFS:```bashcagefsctl --force-update```---### 4 VerificationVerify CageFS picked up the new configs:```bashansible cpanel_servers -m shell -a "ls /etc/cagefs/conf.d/ | grep -E 'node|composer'"```**Quick runtime verification inside a user's CageFS environment**Replace `username` with a real user on the target host.**Option A Using cagefs_enter**```bashansible cpanel_servers -m shell -a "/usr/sbin/cagefs_enter username composer --version || /usr/sbin/cagefs_enter username node -v"```**Option B Using sudo to run as the user**```bashansible cpanel_serverX -m shell -a "sudo -u username -H bash -c 'composer --version 2>/dev/null || node -v 2>/dev/null || echo \"composer/node not available inside CageFS\"'"```deploy_node_and_composer.sh
#!/bin/bash################################################################################# Script Name: deploy_node_and_composer.sh# Usage: ./deploy_node_and_composer.sh <node_version> <app_name># Examples:# ./deploy_node_and_composer.sh alt-nodejs20 composer# ./deploy_node_and_composer.sh ea-node composer## Description:# 1. Sets global symlinks for node/npm/npx in /usr/local/bin.# 2. Installs/updates Composer globally.# 3. Configures CageFS for both Node.js and Composer.# 4. Force updates CageFS.################################################################################CHOICE=$1APP=$2if [ -z "$CHOICE" ] || [ -z "$APP" ]; then echo "Usage: $0 <node_version> <app_name>" echo "Example: $0 alt-nodejs24 composer" echo "Example: $0 ea-node composer" exit 1fi# Pre-flight check: Verify version existsif [ "$CHOICE" != "ea-node" ]; then if [ ! -d "/opt/alt/${CHOICE}" ]; then echo "Error: ${CHOICE} not found in /opt/alt/. Aborting." exit 1 fifi# Logic for Node.js Symlinks & CageFS Pathsif [ "$CHOICE" = "ea-node" ]; then # Standard cPanel/EA4 Paths BASE_PATH="/usr/bin" CFG_NAME="ea-node" PATHS="/usr/bin/node,/usr/bin/npm,/usr/bin/npx,/usr/local/bin/node,/usr/local/bin/npm,/usr/local/bin/npx"else # CloudLinux Alt-NodeJS Paths BASE_PATH="/opt/alt/${CHOICE}/root/usr/bin" CFG_NAME="${CHOICE}" PATHS="/opt/alt/${CHOICE}/root/usr/bin/node,/opt/alt/${CHOICE}/root/usr/bin/npm,/opt/alt/${CHOICE}/root/usr/bin/npx,/opt/alt/${CHOICE}/root/usr/lib/node_modules,/usr/local/bin/node,/usr/local/bin/npm,/usr/local/bin/npx"fi# Apply Symlinksln -sf ${BASE_PATH}/node /usr/local/bin/nodeln -sf ${BASE_PATH}/npm /usr/local/bin/npmln -sf ${BASE_PATH}/npx /usr/local/bin/npx# Install Composerif [ "$APP" = "composer" ]; then wget https://getcomposer.org/composer.phar -O /usr/local/bin/composer chmod +x /usr/local/bin/composer echo -e "[composer]\ncomment=Composer global access\npaths=/usr/local/bin/composer" > /etc/cagefs/conf.d/composer.cfgfi# Create Node CageFS configecho -e "[${CFG_NAME}]\ncomment=${CHOICE} global access\npaths=${PATHS}" > /etc/cagefs/conf.d/nodejs.cfg# Finalizecagefsctl --force-updateecho "=== Configured ${CHOICE} and ${APP} globally inside CageFS ==="Validate & Reload
cagefsctl --force-updateRedis
As for Redis, I decided to make a seperate article in its regard. When providing Redis in a shared environment, isolation is the primary challenge. Without a specific containerized approach like Podman or a specialized Litespeed integration, a single global Redis instance is a massive security risk because every user could theoretically read or flush the cache of every other user.
- How to install Redis on CloudLinux servers with CageFS
- Is it possible to add Redis to CageFS with isolate data for each hosting account
The only real issue I currently see, is the ambiguaty it may create for those who are not using to deal with node.js, they may think that’s the way to run a node.js app (it won’t work anyway as ports are already closed), but they may exploit it in cron as well. Not they were not able to do it before with CL Selector Apps, but it may add some confusion while fixing the initial issue.
This is by design how the selector works, and not something we can workarround unless we add some notifications in cPanel or add a wrapping to the commands themselves saying that this is explicitly for x, y, z usage and for node.js apps rather use: a, b, c..;