Modern shared hosting with cPanel supports Node.js through Apache Passenger. This makes it possible to run Express, Next.js, and other Node.js applications without renting a VPS — Passenger manages the Node.js process the same way it usually manages PHP-FPM workers. The main difference from a classic VPS deployment is that you do not need to set up a reverse proxy or a systemd service yourself — Apache handles the whole process lifecycle (starting, restarting on a crash, stopping when idle) through the mod_passenger module.
Running Node.js through cPanel
Step 1: Setup Node.js App
cPanel → Software → Setup Node.js App → Create Application.
| Field | Example |
|---|---|
| Node.js version | 20.x LTS |
| Application mode | Production |
| Application root | myapp |
| Application URL | example.com/app or a subdomain |
| Application startup file | app.js |
Step 2: Upload the files
# Application structure in ~/myapp/
myapp/
├── app.js (entry point)
├── package.json
└── node_modules/ (created by npm install)
Step 3: Install dependencies
In Setup Node.js App click "Run NPM Install", or over SSH:
cd ~/myapp
npm install
Step 4: A minimal app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello from Node.js on cPanel!');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Common startup error
If dependencies were not installed or the wrong startup file is specified, visiting the site shows the standard Passenger error page with this text:
Web application could not be started
Error: Cannot find module 'express'
Require stack:
- /home/user/myapp/app.js
This means npm install was not run under the same Node.js version specified in Setup Node.js App, or the node_modules folder was copied over from another server — native modules with binary dependencies (for example, bcrypt, sharp) are built for a specific Node.js version and architecture and do not carry over between environments. The fix is to delete node_modules and run npm install again through the Setup Node.js App interface, which activates the right environment automatically.
Environment variables
In the Setup Node.js App interface → Add Variable:
NODE_ENV=production
DATABASE_URL=mysql://user:pass@localhost/dbname
API_KEY=your_secret_key
Passenger automatically manages the process — it restarts on a crash, so PM2 is not needed. The port is assigned automatically through the process.env.PORT variable — do not hardcode 3000 or any other port in production, otherwise the application will fail to start if that port is already taken by another site on the server.
Proxying through .htaccess for a non-root path
# If Application URL points to a subfolder rather than the domain root,
# Setup Node.js App creates .htaccess automatically:
PassengerAppRoot "/home/user/myapp"
PassengerBaseURI "/app"
PassengerAppType node
PassengerStartupFile app.js
These directives are written into the .htaccess of the domain root or subfolder — editing them by hand is usually unnecessary, but if the file is accidentally deleted or overwritten by another deploy, the application stops opening with a 404 error even though the Node.js process is still running in the background. Re-saving the settings in Setup Node.js App usually fixes this — the interface rewrites .htaccess with the correct directives without needing to touch the file by hand.
When Node.js on shared hosting does not fit
- The application needs persistent WebSocket connections with a large number of simultaneous clients — Passenger on shared plans limits the number of processes and is not built for thousands of long-lived sockets
- You need background jobs and queues (cron workers, video processing) — LVE limits the process's CPU and memory, and long-running computations get force-stopped
- You need root access to install system dependencies for native modules — shared hosting has no sudo, so compiling some packages with C++ bindings may fail
- The project uses Docker or requires a specific piece of infrastructure (Redis, RabbitMQ) on the same server — shared cPanel hosting does not provide those services, a VPS is needed instead
Verifying the result
curl -I https://example.com/app/
# Expected: HTTP/1.1 200 OK
# Application logs are located at:
~/nodevenv/myapp/20/logs/
If curl returns 502 Bad Gateway, the Node.js process crashed right after starting — check the output in the Setup Node.js App interface or the Passenger logs, which contain the exact error stack trace rather than just the generic "application could not be started" message a site visitor sees. More on reading error logs in cPanel is in the article reading error logs in cPanel, and on SSH access for debugging in SSH access on cPanel hosting.