Deploy a Node.js App in cPanel: Setup Node.js App Guide
If your hosting plan supports it, cPanel can run a Node.js app for you without you ever touching a systemd service file - it uses Phusion Passenger under the hood to keep your app alive, proxy requests to it, and restart it when it crashes. Most support tickets we see aren't about whether it works, they're about one confusing step in the setup or a cryptic Passenger error after a deploy. Here's how the feature actually works and how to get past the errors that trip people up.
What "Setup Node.js App" Actually Does
In cPanel, under Software, there's a tool called Setup Node.js App. When you create an application there, cPanel does three things behind the scenes:
- Creates an isolated Node.js virtual environment for that app, pinned to whichever Node version you pick
- Registers the app with Passenger so Apache/LiteSpeed can proxy traffic to it on an internal port
- Writes a
.htaccessfile in the application's public directory that routes requests into Passenger
This means you don't manage a port yourself, and you don't run node app.js in a screen session and hope it survives a reboot. Passenger owns the process lifecycle. That's the whole point of the tool - but it's also why the errors look different from a plain Node deployment.
Step-by-Step: Deploying Your First App
1. Create the application
In cPanel, open Setup Node.js App → Create Application and fill in:
- Node.js version - pick the version your app was built and tested against, not just the newest one available
- Application mode -
Productionfor a live site (setsNODE_ENV=production),Developmentonly while you're actively debugging - Application root - a folder outside
public_html, e.g.myapp, which resolves to/home/username/myapp - Application URL - the domain or subdomain that should serve this app
- Application startup file - usually
app.js,server.js, orindex.js- whatever file callsapp.listen()or exports your server
Click Create. cPanel will show you a command to activate the app's virtual environment - copy it, you'll need it in a minute.
2. Upload your code and install dependencies
Upload your project (via Git, SFTP, or File Manager) into the application root you just set. Do not upload node_modules - it gets rebuilt for the environment cPanel created. Then, over SSH, run the activation command cPanel gave you and install:
source /home/username/nodevenv/myapp/18/bin/activate && cd /home/username/myapp
npm install
Skipping the source step is the single most common mistake here. Run npm install without activating the virtual environment first, and packages get installed against the server's system Node (or fail outright with permission errors), not the isolated version cPanel is actually going to run your app with.
3. Set environment variables
Back in the Setup Node.js App interface, scroll to Environment Variables and add whatever your app needs - DATABASE_URL, API_KEY, and so on. PORT is not one of them - Passenger assigns the port internally via process.env.PORT, so don't hardcode a port in your code. Save, then click Restart.
Common Errors and How to Fix Them
| Symptom | Likely Cause | Fix |
|---|---|---|
| "We're sorry, but something went wrong" (Passenger error page) | App crashed on startup, or the startup file path is wrong | Check ~/logs/<domain>.error.log or the app's own log path for the actual stack trace |
Error: Cannot find module 'express' | npm install was run without activating the virtual environment | Re-run source .../bin/activate then npm install from the application root |
App works via node app.js over SSH but not through the browser | App is hardcoded to listen on a specific port instead of process.env.PORT | Change app.listen(3000) to app.listen(process.env.PORT) |
| Changes to code don't show up after redeploy | Passenger caches the running process and doesn't reload files automatically | Click Restart in Setup Node.js App, or run touch tmp/restart.txt in the app root |
EACCES: permission denied during npm install | Files were uploaded as a different user or with root-owned permissions from a prior manual install | Fix ownership with chown -R username:username /home/username/myapp, then reinstall |
| Static assets (CSS/images) 404 even though the app runs | The .htaccess Passenger routing rules are intercepting requests meant for static files | Serve static files through Express (express.static()) rather than relying on Apache to find them directly |
Restarting Correctly After a Deploy
Every time you pull new code or change an environment variable, the app needs a restart - Passenger won't pick up changes on its own. You have two options:
- Click Restart in the Setup Node.js App UI - the reliable option, works every time
- Run
touch tmp/restart.txtinside the application root over SSH - useful if you're scripting deploys with Git hooks
If you're pulling code via a post-receive Git hook, add the touch tmp/restart.txt command as the last line of the hook so every push restarts the app automatically.
Checking Logs When Something's Wrong
Passenger's own errors land in cPanel's domain error log, but anything your app logs with console.log or a logging library goes to stdout.log/stderr.log inside the application's log folder, viewable from the Setup Node.js App page itself under the app's detail view. Check both - a database connection failure, for instance, usually shows up in your app's own log, not Apache's.
Prevention
- Pin your Node version in
package.json'senginesfield so it's obvious which version the app expects - Never commit
node_modulesor a.envfile - use cPanel's Environment Variables panel for secrets instead - Test locally with
NODE_ENV=productionbefore deploying, since some packages behave differently in production mode - Keep a lightweight
/healthroute in your app that just returns 200 - it makes it much faster to tell "is Passenger running my app at all" apart from "is my app logic broken"
Frequently asked questions
Can I run a Node.js app on shared hosting, or do I need a VPS?
Setup Node.js App is available on most shared and reseller cPanel plans that have it enabled, not just VPS. Resource limits (CPU/memory via CloudLinux LVE) still apply, so a heavy app will hit those limits faster on shared hosting than on a VPS.
Why does my app work when I run it manually over SSH but not through the domain?
This almost always means the app is listening on a hardcoded port instead of process.env.PORT. Passenger assigns the port internally and proxies requests to it - if your code ignores that variable, Passenger can't reach the process.
Do I need to restart the app every time I edit a file?
Yes. Unlike a local dev server with hot-reload, Passenger runs your app as a long-lived process and won't notice file changes on its own. Click Restart in Setup Node.js App or touch tmp/restart.txt after every deploy.
Can I use PM2 instead of Passenger?
You can install PM2 inside the app's virtual environment, but it's redundant - Passenger already handles process supervision, restarts on crash, and port proxying. Running PM2 on top usually just adds a second layer of process management fighting with the first.
Where do I find my app's actual error output?
Open Setup Node.js App, click into the application, and check the log files listed there (stdout/stderr), plus the domain's error log under cPanel's Errors or Metrics section for Passenger-level failures like a missing startup file.