This article is published in English.
Deploying a Node.js App on cPanel Shared Hosting With Passenger
A step-by-step guide to running an Express app on cPanel shared hosting with Application Manager and Passenger, including restarts, env variables and 503 fixes.
Many cPanel shared hosts can run an Express app or API server well, provided the account has Node.js support with Phusion Passenger behind cPanel's Application Manager. You do not configure Nginx, an Apache reverse proxy or PM2 yourself; Passenger starts the app and routes requests to it. This guide covers the full deployment, from checking the feature is enabled to diagnosing a 503, and ends with a pre-launch checklist.
What your hosting account needs
Confirm the account offers the following before you upload anything:
- Node.js support
- cPanel Application Manager
- Passenger
- Terminal or SSH access
- npm
- a domain or subdomain to serve the app from
- database access, if the app needs one
If Application Manager is missing, ask your host to enable Node.js and Passenger. Hosts running CloudLinux may label the tool differently.
Step 1: Confirm Node.js is available
In cPanel, open Software and then Application Manager (some versions show Node.js options under website management instead). If it opens, the account is ready.
Step 2: Upload the application
Upload the project with File Manager or Git into a folder in your home directory, for example:
/home/username/my-node-app
A typical project layout looks like this:
my-node-app/
├── package.json
├── package-lock.json
├── app.js
├── src/
└── ...
Keep the source outside public_html, because browsers can request anything placed there directly.
Step 3: Prepare package.json and install dependencies
The project needs a valid package.json that declares its dependencies and a start script. A minimal Express example:
{
"name": "my-node-app",
"version": "1.0.0",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^5.1.0"
}
}
Then open cPanel's Terminal, move into the project folder and install:
cd ~/my-node-app
npm install
This installs the declared dependencies on the server. With a committed lock file, npm ci --omit=dev is a lighter, reproducible alternative.
Step 4: Write the startup file
Passenger needs an entry point that it can launch, such as:
app.js
For an Express app, that file starts by loading Express:
const express = require('express');
and then creates the app, defines a route and starts listening. The code below is compressed onto a few lines, but it is valid JavaScript because each statement ends with a semicolon.
const app = express();const PORT = process.env.PORT || 3000;app.get('/', (req, res) => {
res.send('Node.js application is working!');
});app.listen(PORT, '0.0.0.0', () => {
console.log(`Application running on port ${PORT}`);
});
Why the port must come from process.env.PORT
Do not hard-code a public port. Passenger decides how requests reach your process, so read the port from the environment and keep a local fallback:
const PORT = process.env.PORT || 3000;
The same code then runs locally on port 3000 and under Passenger unchanged.
Step 5: Register the application in cPanel
In Application Manager, click Create Application or Register Application. Give the app a name such as my-node-app, pick the domain (for instance example.com) and / as the base URL, set the root to the project folder and the startup file to app.js, choose a stable Node.js version your dependencies support, and select the Production environment. Then click Create or Deploy. The resulting request path looks like this:
https://example.com
↓
Apache
↓
Passenger
↓
Node.js App
↓
app.js
Apache receives the request, and Passenger forwards it to the Node.js process started from your startup file.
Step 6: Set environment variables
Define environment variables in the application's settings rather than in code. For example:
APP_ENV=production
DB_HOST=localhost
DB_DATABASE=mydb
DB_USERNAME=myuser
DB_PASSWORD=your_password
Your code reads them through process.env:
process.env.DB_HOST
process.env.DB_DATABASE
process.env.DB_USERNAME
Keep secrets server-side only: never in frontend JavaScript or a publicly reachable file such as a .env in public_html.
Step 7: Restart after every change
Passenger keeps the app loaded, so changes apply only after a restart from Application Manager. Where the restart-file convention is supported, the terminal works too:
mkdir -p ~/my-node-app/tmp
touch ~/my-node-app/tmp/restart.txt
Passenger watches the timestamp of tmp/restart.txt and restarts the app on the next request after it changes, which suits deploy scripts.
Troubleshooting a 503 Service Unavailable
The error you are most likely to meet is this one:
503 Service Unavailable
A 503 rarely means the server is down; usually Passenger could not start or reach your app. Run it yourself first:
cd ~/my-node-app
node app.js
If it crashes, fix that first. If it starts, check the usual suspects.
Wrong startup file
Application Manager must point at the file that actually exists and starts the server:
app.js
Missing dependencies
If node_modules is absent or incomplete, install again:
npm install
Incompatible Node.js version
Check which version the terminal is using and what your dependencies require:
node -v
Then pick a matching version in the app settings.
Hard-coded port
Make sure the server listens on:
process.env.PORT
rather than a fixed public port.
Missing environment variables
Confirm credentials, API keys, the app mode and other required values are set; an undefined variable that crashes startup also yields a 503.
Logs
The Passenger or cPanel application logs usually show the exact startup error.
Shared hosting versus a VPS
The two environments differ mainly in who controls the server. On cPanel shared hosting, the stack looks roughly like this:
cPanel
│
├── Apache
├── Passenger
└── Node.js
│
└── Your Application
The host and Passenger manage the web server and process. On a VPS, you own every layer:
VPS
│
├── Nginx/Apache
├── Node.js
├── PM2
├── Firewall
├── SSL
└── Application
A VPS offers much more control and usually suits resource-heavy or highly customized apps better; see a practical framework for taking Node.js apps to production. Shared hosting trades that control for simplicity.
A clean directory layout
A tidy cPanel deployment keeps the app and the public web root side by side:
/home/username/
│
├── my-node-app/
│ ├── app.js
│ ├── package.json
│ ├── package-lock.json
│ ├── node_modules/
│ ├── src/
│ └── tmp/
│ └── restart.txt
│
└── public_html/
Requests reach my-node-app through Apache and Passenger, and public_html stays free of server code.
Final checklist
Before you consider the deployment complete, confirm that:
- Node.js is enabled for the account
- Application Manager is available
- a compatible Node.js version is selected
- the project files are uploaded outside
public_html package.jsonis presentnpm installfinished without errors- the startup file setting is correct
- the server listens on
process.env.PORT - environment variables are configured
- the environment is set to Production
- the application has been restarted since the last change
- the domain loads in a browser
- the logs show no startup errors
Wrapping up
On cPanel shared hosting, Application Manager with Passenger is the dependable route, letting you run Express apps and APIs without root access. Let Passenger own the port, restart after every change, and when something breaks, run the app manually and read the logs before changing configuration.