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/composer
chmod +x /usr/local/bin/composer
cat > /etc/cagefs/conf.d/composer.cfg <<EOF
[composer]
comment=Composer global access
paths=/usr/local/bin/composer
EOF2. Global Node.js + NPM (alt-nodejs24)
# Symlinks (auto in PATH)</em>
ln -sf /opt/alt/alt-nodejs24/root/usr/bin/node /usr/local/bin/node
ln -sf /opt/alt/alt-nodejs24/root/usr/bin/npm /usr/local/bin/npm
ln -sf /opt/alt/alt-nodejs24/root/usr/bin/npx /usr/local/bin/npx
cat > /etc/cagefs/conf.d/nodejs.cfg <<EOF
[alt-nodejs24]
comment=CloudLinux Node.js 24 global access
paths=/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
EOF(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/mynodeapp
node -b
Automation: 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 Ansible
Utility to globally enable **Node.js** (EA-Node or CloudLinux Alt) and **Composer** inside CageFS across the cluster.
---
### Overview
This 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 Versions
Run this command to detect installed Node.js variants on the target server:
```bash
ansible 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 Missing
Install the desired Node.js package on the target server via yum.
**EA Node.js example (v22):**
```bash
ansible cpanel_servers -m shell -a "yum install -y ea-nodejs22"
```
**Alt Node.js example (v24):**
```bash
ansible 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 Usage
Use the Ansible `script` module to run the deployment script that creates symlinks and writes CageFS config files.
**Option A CloudLinux Alt-Node**
```bash
ansible cpanel_servers -m script -a "deploy_node_and_composer.sh alt-nodejs24 composer"
```
**Option B EA-Node**
```bash
ansible cpanel_servers -m script -a "deploy_node_and_composer.sh ea-node composer"
```
---
### Example CageFS Configs and Composer install
Place the following example CageFS config for Node.js under `/etc/cagefs/conf.d/nodejs.cfg`:
```ini
[alt-nodejs24]
comment=CloudLinux Node.js 24 global access
paths=/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:
```bash
wget https://getcomposer.org/composer.phar -O /tmp/composer.phar
chmod +x /tmp/composer.phar
mv /tmp/composer.phar /usr/local/bin/composer
```
Create CageFS config for Composer at `/etc/cagefs/conf.d/composer.cfg`:
```ini
[composer]
comment=Composer global access
paths=/usr/local/bin/composer
```
After updating configs, refresh CageFS:
```bash
cagefsctl --force-update
```
---
### 4 Verification
Verify CageFS picked up the new configs:
```bash
ansible 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**
```bash
ansible 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**
```bash
ansible 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=$1
APP=$2
if [ -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 1
fi
# Pre-flight check: Verify version exists
if [ "$CHOICE" != "ea-node" ]; then
if [ ! -d "/opt/alt/${CHOICE}" ]; then
echo "Error: ${CHOICE} not found in /opt/alt/. Aborting."
exit 1
fi
fi
# Logic for Node.js Symlinks & CageFS Paths
if [ "$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 Symlinks
ln -sf ${BASE_PATH}/node /usr/local/bin/node
ln -sf ${BASE_PATH}/npm /usr/local/bin/npm
ln -sf ${BASE_PATH}/npx /usr/local/bin/npx
# Install Composer
if [ "$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.cfg
fi
# Create Node CageFS config
echo -e "[${CFG_NAME}]\ncomment=${CHOICE} global access\npaths=${PATHS}" > /etc/cagefs/conf.d/nodejs.cfg
# Finalize
cagefsctl --force-update
echo "=== 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..;