DEV Community

Cover image for How to Deploy and Run a NestJS App on Namecheap Shared Hosting
Oluwadara Adesoji
Oluwadara Adesoji

Posted on

How to Deploy and Run a NestJS App on Namecheap Shared Hosting

Deploying NestJS to Namecheap Shared Hosting is slightly different from deploying a static website or a PHP application.

NestJS must be compiled from TypeScript to JavaScript, its production dependencies must be installed, and cPanel's Node.js application manager must start it through Passenger. External services such as Redis must also be reachable from the shared server.

I learned those details while deploying a NestJS API with Bull queues, Redis, Swagger, PDF uploads, and a custom domain. This guide collects the complete working process—including the problems that initially left me with an empty deployment folder, missing build files, unavailable routes, and failed Redis connections.

How the Production Setup Works

The final request path looks like this:

Browser or API client
        |
        v
Domain and HTTPS
        |
        v
Apache / Passenger on Namecheap
        |
        v
app.js
        |
        v
dist/main.js
        |
        v
NestJS application
Enter fullscreen mode Exit fullscreen mode

You do not need PM2 for this setup. Namecheap's Setup Node.js App interface and Passenger manage the application process.

Namecheap currently lets shared-hosting customers choose the Node.js version, application mode, application root, URL, startup file, and environment variables from cPanel. Its official instructions are available in How to work with Node.js App.

Prerequisites

Before starting, you need:

  • A Namecheap Shared Hosting account with Node.js support
  • A domain or subdomain assigned to the hosting account
  • Access to cPanel
  • SSH or cPanel Terminal access
  • A working NestJS project
  • A supported Node.js version compatible with the project
  • Production credentials for any external database, Redis server, or API

Run the application locally before deploying:

npm install
npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Fix local build or startup errors first. Production adds enough moving parts without bringing local errors along for the ride.

1. Prepare NestJS for Production

Listen on the port supplied by the host

Namecheap's Passenger environment controls how requests reach the application. Do not rely exclusively on a hard-coded local port.

In src/main.ts, listen on process.env.PORT with a local fallback:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  await app.listen(process.env.PORT || 4000);
}

bootstrap();
Enter fullscreen mode Exit fullscreen mode

The fallback is useful locally. In production, the host-provided PORT value takes priority.

Add production scripts

Your package.json should include at least:

{
  "scripts": {
    "build": "nest build",
    "start:prod": "node dist/main.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Keep runtime packages in dependencies

Packages needed after compilation belong in dependencies, not devDependencies.

This includes packages such as:

@nestjs/common
@nestjs/core
@nestjs/platform-express
reflect-metadata
rxjs
Enter fullscreen mode Exit fullscreen mode

The Nest CLI, TypeScript, test tools, and linters can remain in devDependencies.

This distinction matters because a production-only install may omit devDependencies. The build can exist and still crash immediately if a required Nest package was classified as development-only.

2. Make the Build Produce dist/main.js

Namecheap runs JavaScript, not the TypeScript source directly. Compile the application:

npm run build
Enter fullscreen mode Exit fullscreen mode

The expected entry file for this guide is:

dist/main.js
Enter fullscreen mode Exit fullscreen mode

Use a focused tsconfig.build.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "./src"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
Enter fullscreen mode Exit fullscreen mode

Restricting the production build to src/ prevents root-level JavaScript files such as app.js from being type-checked and emitted. It also avoids a layout such as dist/src/main.js when the desired output is dist/main.js.

Verify the build:

ls -l dist/main.js
node dist/main.js
Enter fullscreen mode Exit fullscreen mode

If the application starts, stop it with Ctrl+C.

3. Create Passenger's Startup File

Create app.js in the project root:

require('./dist/main');
Enter fullscreen mode Exit fullscreen mode

Your deployment should now resemble:

application-root/
├── app.js
├── package.json
├── package-lock.json
├── dist/
│   └── main.js
└── src/
Enter fullscreen mode Exit fullscreen mode

Passenger conventionally looks for app.js, although cPanel also lets you specify another startup filename. The cPanel documentation likewise recommends app.js as the standard startup file: How to Install a Node.js Application.

The startup bridge is deliberately small. It loads the compiled NestJS application; it does not create a second server.

4. Decide Where to Build

There are two sensible deployment strategies.

Option A: Build locally and upload dist

This is usually safer on shared hosting:

npm ci
npm run build
Enter fullscreen mode Exit fullscreen mode

Upload:

  • app.js
  • dist/
  • package.json
  • package-lock.json

Then install only runtime dependencies on Namecheap:

npm ci --omit=dev
Enter fullscreen mode Exit fullscreen mode

This approach reduces CPU and memory usage on the shared server.

If dist/ is listed in .gitignore, a Git-only deployment will not send it. Use an artifact upload, SFTP, rsync, or another deployment step that deliberately transfers the build output.

Option B: Build on Namecheap

Upload the source and install all build dependencies:

npm ci --include=dev
npm run build
npm prune --omit=dev
Enter fullscreen mode Exit fullscreen mode

This keeps dist/ out of source control but requires enough server resources to run the Nest compiler.

Choose one strategy and make it explicit. A common failure is ignoring dist/, not building on the server, and then asking Passenger to load a file that does not exist.

5. Upload the Project

You can use:

  • cPanel File Manager
  • SFTP
  • rsync over SSH
  • cPanel Git Version Control
  • A custom Git deployment remote

Do not upload:

  • Your local node_modules/
  • Local logs
  • Test coverage
  • Editor configuration
  • Development secrets

Install dependencies for the Node version selected in cPanel instead of copying node_modules from your computer. Native modules compiled on macOS, for example, may not run on the Linux hosting server.

If Git creates only a .git directory

If the remote folder contains .git but no application files, the pushed branch may not be the branch checked out by the server repository.

For example, the server may initialize master while you push main.

On the server:

cd ~/your-application-root
git branch -a
git checkout -f main
Enter fullscreen mode Exit fullscreen mode

Replace main with the branch you actually pushed.

Renaming a local branch can also align the two sides:

git branch -m main master
Enter fullscreen mode Exit fullscreen mode

The important detail is not whether the branch is called main or master; it is that the server worktree checks out the deployed branch.

6. Create the Node.js Application in cPanel

Open:

Namecheap Dashboard
→ Hosting List
→ Go to cPanel
→ Software
→ Setup Node.js App
→ Create application
Enter fullscreen mode Exit fullscreen mode

Choose:

  • Node.js version: Match the version supported by your project
  • Application mode: Production
  • Application root: The folder containing app.js
  • Application URL: Your domain or subdomain
  • Application startup file: app.js

The application root must point to the directory that contains app.js, package.json, and dist/. Do not point it directly at dist.

Create the application.

7. Install Dependencies in the Correct Environment

The Node.js application page shows a command for entering its virtual environment through SSH. Copy and run that command exactly.

Then enter the application root:

cd ~/your-application-root
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

npm ci --omit=dev
Enter fullscreen mode Exit fullscreen mode

If you chose to build on the server instead:

npm ci --include=dev
npm run build
npm prune --omit=dev
Enter fullscreen mode Exit fullscreen mode

You can also use the Run NPM Install button in the cPanel Node.js application interface. Namecheap documents both the virtual-environment command and the cPanel install action in its Node.js App guide.

8. Configure Environment Variables

In Setup Node.js App, edit the application and add the required variables:

NODE_ENV=production
OPENAI_API_KEY=...
REDIS_URL=...
DATABASE_URL=...
Enter fullscreen mode Exit fullscreen mode

Only add variables your application actually uses.

You may also use a .env file in the application root:

application-root/
├── .env
├── app.js
├── package.json
└── dist/
Enter fullscreen mode Exit fullscreen mode

Do not put .env inside dist.

Protect it with .gitignore:

.env
.env.*
!.env.example
Enter fullscreen mode Exit fullscreen mode

Do not put real credentials in .env.example. If a secret has ever been committed or published, removing it from the latest file is not enough—rotate the credential.

When a variable exists in both cPanel and .env, the existing process environment normally takes precedence. Make sure an old cPanel value is not overriding the current .env value.

Restart the application after changing environment variables.

9. Start or Restart the App

Return to:

cPanel → Setup Node.js App
Enter fullscreen mode Exit fullscreen mode

Use Start App or Restart.

Do not keep the application alive by running npm run start:prod in an SSH terminal. The process will be disconnected from the hosting lifecycle, may conflict with Passenger, and can produce EADDRINUSE errors.

Passenger should own the production process.

For environments using a direct Passenger configuration, cPanel also documents the tmp/restart.txt mechanism. On Namecheap's managed interface, the restart button is simpler.

10. Test the Deployment

Start with a simple GET route:

https://api.example.com/v1
Enter fullscreen mode Exit fullscreen mode

If you use NestJS URI versioning:

app.enableVersioning({
  type: VersioningType.URI,
  defaultVersion: '1',
});
Enter fullscreen mode Exit fullscreen mode

then controller routes receive the /v1 prefix.

For example:

@Controller('api/parse-bank-statement')
export class PdfParserController {
  @Get('job/:jobId')
  getJobStatus() {}
}
Enter fullscreen mode Exit fullscreen mode

becomes:

GET /v1/api/parse-bank-statement/job/:jobId
Enter fullscreen mode Exit fullscreen mode

Test from a terminal:

curl -i https://api.example.com/v1
Enter fullscreen mode Exit fullscreen mode

A NestJS JSON response—even a NestJS-formatted 404—proves that the request reached the application. It does not necessarily mean the proxy or deployment failed.

11. Understand Why Swagger May Return 404

Many applications intentionally disable Swagger in production:

if (process.env.NODE_ENV !== 'production') {
  setupSwaggerDocs(app);
}
Enter fullscreen mode Exit fullscreen mode

In that case:

GET /docs
Enter fullscreen mode Exit fullscreen mode

returns:

{
  "message": "Cannot GET /docs",
  "error": "Not Found",
  "statusCode": 404
}
Enter fullscreen mode Exit fullscreen mode

That response is expected. The NestJS application is running, but the documentation route was never registered.

Do not use /docs as your only health check when production configuration disables it.

12. Read Namecheap Production Logs

Open the Passenger log configured for the application. It is often named stderr.log.

Display recent errors:

tail -n 100 stderr.log
Enter fullscreen mode Exit fullscreen mode

Follow new entries:

tail -f stderr.log
Enter fullscreen mode Exit fullscreen mode

Press Ctrl+C to stop following.

Find other logs:

find . -maxdepth 3 -type f -name "*.log"
Enter fullscreen mode Exit fullscreen mode

Also check:

cPanel → Metrics → Errors
Enter fullscreen mode Exit fullscreen mode

Namecheap says this interface displays the latest web-server error entries: cPanel control panel overview.

13. Connect External Services Carefully

A working HTTP route does not prove that Redis, a database, or another API is reachable.

For Redis-backed Bull queues, test the TCP port from Namecheap:

nc -vz <redis-host> <redis-port>
Enter fullscreen mode Exit fullscreen mode

Then send a real Redis PING:

node -r dotenv/config -e 'const Redis=require("ioredis"); const r=new Redis(process.env.REDIS_URL,{connectTimeout:5000,maxRetriesPerRequest:1,retryStrategy:()=>null}); r.on("error",()=>{}); r.ping().then(console.log).catch(e=>console.error(e.code,e.message)).finally(()=>r.disconnect())'
Enter fullscreen mode Exit fullscreen mode

Success returns:

PONG
Enter fullscreen mode Exit fullscreen mode

If Redis works locally but Namecheap reports ECONNREFUSED or ETIMEDOUT, check:

  • The Redis server's firewall
  • Cloud security-group rules
  • Redis source-IP allowlists
  • Whether Namecheap permits the outbound destination port

In my deployment, Namecheap Support had to permit the Redis port before the production application could connect.

For the full Redis setup and debugging process, see How to Connect Redis to a Node.js App on Namecheap Shared Hosting.

14. Await Important Asynchronous Operations

Production networking exposes bugs that local development may hide.

For example, do not queue a job without awaiting the promise:

this.processPdfQueue.add(jobData);
Enter fullscreen mode Exit fullscreen mode

Use:

const job = await this.processPdfQueue.add(jobData);
Enter fullscreen mode Exit fullscreen mode

Without await, the controller can return “queued successfully” before Redis has accepted the job. A later unhandled rejection can also terminate the Node.js process.

Common Deployment Errors

Error or symptom Likely cause What to check
Only .git appears on the server The pushed branch is not checked out Run git branch -a and check out the deployed branch
Cannot find module './dist/main' Missing build or wrong output layout Run the build and verify dist/main.js
EADDRINUSE Another process already uses the fallback port Stop the manual process and let Passenger manage the app
Cannot GET /docs Swagger is disabled in production Test a real API or health route
NestJS-formatted 404 The app is running, but that route does not exist Check version prefixes and controller paths
MaxRetriesPerRequestError Redis requests repeatedly failed Find the underlying connection error
ECONNREFUSED to an external service Port closed, service stopped, or connection rejected Test with nc; inspect firewalls and hosting rules
Environment variable appears stale cPanel value overrides .env, or app was not restarted Check both locations and restart
App works in terminal but not through domain cPanel application root, URL, or startup file is wrong Verify the Node.js App configuration
App crashes after production-only install Runtime package is in devDependencies Move runtime packages to dependencies

Production Checklist

Before considering the deployment complete, verify:

  • The application builds locally
  • dist/main.js exists
  • Root-level app.js loads ./dist/main
  • Runtime Nest packages are in dependencies
  • node_modules was installed on the server, not copied from macOS or Windows
  • The cPanel application root contains app.js
  • The cPanel startup file is app.js
  • The selected Node.js version matches the project
  • NODE_ENV is set correctly
  • Secrets are not committed to Git
  • The app was restarted after environment changes
  • A simple GET route works through HTTPS
  • Expected version prefixes such as /v1 are included
  • Production logs are accessible
  • External databases and Redis are reachable from Namecheap
  • Important asynchronous operations are awaited

Final Takeaway

Running NestJS on Namecheap Shared Hosting is entirely workable once the responsibilities are clear:

  • Nest compiles the TypeScript application into dist.
  • app.js gives Passenger a predictable startup file.
  • cPanel selects the Node version, root directory, URL, mode, and environment.
  • Passenger owns the process and production port.
  • Your deployment method must transfer or create dist.
  • External services must accept connections from the Namecheap server.
  • stderr.log tells you what happened when the browser only shows a generic error.

Most of the difficult failures were not mysterious NestJS bugs. They were boundary problems between the build, Git worktree, Passenger configuration, environment variables, and network access. Test those boundaries one at a time, and the deployment becomes much easier to reason about.

Top comments (0)