On this page
Migrating an Existing Node.js Application
Move your existing Node.js app to Kualo hosting with a few small adjustments for cPanel's Node.js Selector.
If you're moving a Node.js application from your local machine or a VPS, you'll likely need to make a few small adjustments before it will run successfully under cPanel's Node.js Selector.
The Node.js Selector runs your app within a managed hosting environment that uses Phusion Passenger for routing and process management. Unlike a local setup where your app runs its own web server and binds to a custom port like 3000, this environment:
- Does not allow custom ports - apps cannot listen on 3000, 8080, etc.
- Does not use your
npm startscript - it uses the startup file you define in cPanel. - Requires your app to export a handler, not run a standalone HTTP server.
Some apps written using ES module syntax (import/export) may also need changes depending on the Node.js version in use and how the app is structured.
This guide walks you through adapting your existing app to work in this environment - including setting it up correctly, modifying the startup script, and handling route paths if you're deploying under a subdirectory.
Step 1: create the app first
Even if you have all your files ready to go, register the application in cPanel before uploading any code.
- Create a new application using the Node.js Selector, or set up your app via the command line.
- Set the Application Root to the directory you will upload your app into.
- Specify the Startup File - for example
app.jsorserver.js. This must match the actual filename of your app's main entry point.
Creating the app first ensures your environment is ready before you upload code, avoiding conflicts or permission errors.
Step 2: upload your application
Once the app is created:
- Use File Manager, Git, or SFTP to upload your application files into the Application Root directory you chose in step 1.
- Make sure to include your
package.json.
Step 3: adjust the startup script
Most apps developed elsewhere will try to bind to a specific port (such as 3000 or 3001), or use ES module syntax (import) instead of require. Both need attention.
Remove hardcoded ports
You must not use app.listen(3000) or any other fixed port under Passenger. You have two options.
Option A: export the app (recommended)
This is the cleanest approach. Simply export your app and let Passenger handle the rest:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
// Don't call listen - just export.
module.exports = app;
Passenger will start your app and connect incoming requests to it automatically.
Option B: use a dynamic port
If your framework expects app.listen() to be called, you can pass 0 to let Node.js pick any available port:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
const server = app.listen(0, () => {
console.log('App running on port', server.address().port);
});
Passing 0 satisfies Passenger without causing port conflicts.
Switch import to require (if needed)
If your app uses ES module syntax and you encounter errors, replace import statements with require:
// Before:
import express from 'express';
// After:
const express = require('express');
This improves compatibility across different Node.js versions.
Step 4: finalise and restart
- Confirm your
package.jsonis present. It must be in the Application Root directory and list all dependencies your app needs, such as Express. - Run NPM Install. In cPanel, go to Setup Node.js App, click the pencil icon to edit your app, then click Run NPM Install. This installs all dependencies listed in
package.jsoninto your app's private environment. - Restart the application. Still on the Node.js Selector screen, click Restart to reload your app with the correct environment.
- Visit your Application URL. Load the URL you configured in a browser. If everything is set up correctly, your application should be running.
Tips for route handling
If your application is configured to run from a subdirectory - for example https://example.com/myapp - your routes must match that structure.
The Node.js Selector mounts your application at the exact URL path you define in the Application URL field. It does not treat / as the root path by default; it treats /myapp as the entry point.
What can go wrong
If your route is written like this:
app.get('/', (req, res) => res.send('Hello!'));
It will only respond to requests at https://example.com/ - but if your app is mounted at https://example.com/myapp, this route will never be reached. Instead, you will see:
Cannot GET /myapp
Fix 1: prefix routes manually
Rewrite your routes to include the subdirectory:
app.get('/myapp', (req, res) => res.send('Hello from myapp!'));
app.get('/myapp/test', (req, res) => res.send('This is /test'));
Fix 2: use a router with a mount point
A cleaner approach is to use an Express router and mount it at the subdirectory path:
const express = require('express');
const app = express();
const router = express.Router();
router.get('/', (req, res) => res.send('Hello from root!'));
router.get('/test', (req, res) => res.send('This is /test'));
app.use('/myapp', router);
module.exports = app;
This keeps your routing code clean and avoids repeating /myapp throughout your codebase.