Kualo / docs
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.

4 min read Updated 4 Jun 2026

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 start script - 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.js or server.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

  1. Confirm your package.json is present. It must be in the Application Root directory and list all dependencies your app needs, such as Express.
  2. 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.json into your app's private environment.
  3. Restart the application. Still on the Node.js Selector screen, click Restart to reload your app with the correct environment.
  4. 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.

Was this helpful?
Your feedback helps us find gaps in the docs.
Still need a hand?
Real people, around the clock - start a chat or open a ticket and we'll help you put it right.