DEV Community

Cover image for Deploy Angular 16/17/18/19 on cPanel - The Version-Specific Guide ⚡
Saurabh Sharma
Saurabh Sharma

Posted on

Deploy Angular 16/17/18/19 on cPanel - The Version-Specific Guide ⚡

Most Angular deployment guides are written for one version, then quietly become outdated. You follow the steps, something breaks, and you have no idea why — because the guide was for Angular 17 and you're on Angular 19.

This guide is split by version. Find your path. Follow only that section.

Full deep-dive with all details at saurabhsharma.dev

Step 0 — Check Your Angular & Node.js Versions First

sh
ng version
node --version

Note the Angular version number. You'll need it to pick the right path below.

Step 1 — Node.js Compatibility (Read Before Building)

Your local Node.js must match what's available on your cPanel server. Mismatches silently fail npm install on the server.

| Angular Version | Minimum Node.js | Recommended |
|---|---|---|
| Angular 16 | 16.14.x | 18.x LTS |
| Angular 17 | 18.13.x | 18.x or 20.x LTS |
| Angular 18 | 18.19.x | 20.x LTS |
| Angular 19 | 20.11.x | 20.x or 22.x LTS |

Check what your cPanel supports:
cPanel → Setup Node.js App → + CREATE APPLICATION → Node.js version dropdown.

Mismatch? Use nvm to switch locally:

nvm install 20
nvm use 20
node --version  # confirm
Enter fullscreen mode Exit fullscreen mode

Choose Your Path

Does your project use SSR?
│
├─ NO  → Path A — CSR (all versions)
│
└─ YES → Which version?
         ├─ Angular 16  → Path B
         ├─ Angular 17/18 → Path C
         └─ Angular 19  → Path D
Enter fullscreen mode Exit fullscreen mode

How to check if your project uses SSR:

grep -E '"@angular/ssr|@nguniversal' package.json
Enter fullscreen mode Exit fullscreen mode

Line returned = SSR. Nothing = CSR.


Path A — CSR (Static) — All Angular Versions

No Node.js server needed. Just static files + .htaccess.

1. Build

ng build
Enter fullscreen mode Exit fullscreen mode

Important — the output folder changed in Angular 17:

# Angular 16 and below — upload from here:
dist/your-app-name/

# Angular 17 and above — upload from HERE (the browser subfolder):
dist/your-app-name/browser/
Enter fullscreen mode Exit fullscreen mode

Uploading the wrong folder = blank page with no obvious error.

2. Upload to cPanel

Upload the contents of your build folder (not the folder itself) to public_html/.

Via File Manager: Zip the folder → upload → right-click → Extract.

Do NOT upload: node_modules/, .git/, .env, source .ts files.

3. Create .htaccess

This is the step most people miss. Without it, every route except / returns 404.

In cPanel File Manager → Settings → enable Show Hidden Files → create .htaccess:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . /index.html [L]
Enter fullscreen mode Exit fullscreen mode

App in a subfolder (e.g. yourdomain.com/myapp/)? Change RewriteBase / to RewriteBase /myapp/.

4. Test

Visit your domain. Then try a deep route like /about directly in the URL bar. If it loads — you're done.

CSR deployment complete.


Path B — SSR: Angular 16 (Universal)

Angular 16 uses @nguniversal and needs a server.ts modification to work on Apache Passenger.

1. Modify server.ts

Replace this:

function run(): void {
  const port = process.env['PORT'] || 4000;
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}
Enter fullscreen mode Exit fullscreen mode

With this:

function isRunningOnApachePassenger(): boolean {
  return moduleFilename.includes('lsnode.js');
}

function run(): void {
  const server = app();

  if (isRunningOnApachePassenger()) {
    server.listen(() => {
      console.log('Node Express listening to Passenger Apache');
    });
    return;
  }

  const port = process.env['PORT'] || 4000;
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}
Enter fullscreen mode Exit fullscreen mode

Also update the conditional at the bottom of the file:

// Before
if (
  moduleFilename === __filename ||
  moduleFilename.includes('iisnode')
)

// After
if (
  moduleFilename === __filename ||
  moduleFilename.includes('iisnode') ||
  isRunningOnApachePassenger()
)
Enter fullscreen mode Exit fullscreen mode

2. Build

npm run build:ssr
Enter fullscreen mode Exit fullscreen mode

Output:

dist/your-app-name/
├── browser/
└── server/
    └── main.js   ← this is your startup file
Enter fullscreen mode Exit fullscreen mode

3. Upload

Create a directory outside public_html/ — e.g. /home/username/my-angular-app/.

⚠️ SSR apps cannot run from public_html/ on Namecheap shared hosting. This is a cPanel/Passenger constraint.

Zip your project (excluding node_modules/, .git/, README.md, .gitignore), upload there, extract.

4. cPanel Node.js App Setup

Setup Node.js App → + CREATE APPLICATION:

Field Value
Node.js version Match your local (e.g. 18.x)
Application mode Production
Application root /home/username/my-angular-app/
Application URL Your domain
Application startup file dist/your-app-name/server/main.js

Then: Stop → Run NPM Install → Start App.

Angular 16 SSR complete.


Path C — SSR: Angular 17 & 18

No server.ts changes needed. Build command is ng build. Startup file is server.mjs.

1. Build

ng build
Enter fullscreen mode Exit fullscreen mode

Output:

dist/your-app-name/
├── browser/
└── server/
    └── server.mjs   ← startup file (NOT main.js)
Enter fullscreen mode Exit fullscreen mode

2. Upload

Same as Path B — directory outside public_html/. Zip without node_modules/, upload, extract.

3. cPanel Node.js App Setup

Field Value
Node.js version 18.x or 20.x
Application mode Production
Application root /home/username/my-angular-app/
Application URL Your domain
Application startup file dist/your-app-name/server/server.mjs

Then: Stop → Run NPM Install → Start App.

Angular 17/18 SSR complete.


Path D — SSR: Angular 19

SSR is built-in. ES Modules by default. Same server.mjs startup file.

1. Add SSR (if not already set up)

ng add @angular/ssr
Enter fullscreen mode Exit fullscreen mode

2. Add ES Module support to package.json

{
  "name": "my-angular-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "ng build",
    "serve:ssr": "node dist/my-angular-app/server/server.mjs"
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Build

ng build
Enter fullscreen mode Exit fullscreen mode

Output:

dist/your-app-name/
├── browser/
│   ├── index.html
│   └── main.abc123.js
└── server/
    └── server.mjs
Enter fullscreen mode Exit fullscreen mode

4. Upload

Directory outside public_html/. Zip, upload, extract.

5. cPanel Node.js App Setup

Field Value
Node.js version 20.x or 22.x
Application mode Production
Application root /home/username/my-angular-app/
Application URL Your domain
Application startup file dist/your-app-name/server/server.mjs

Stop → Run NPM Install → Start App.

Angular 19 SSR complete.


Startup File Quick Reference

The single most common cPanel mistake — pointing to the wrong file:

Angular Version Startup File
Angular 16 (SSR) dist/your-app-name/server/main.js
Angular 17 (SSR) dist/your-app-name/server/server.mjs
Angular 18 (SSR) dist/your-app-name/server/server.mjs
Angular 19 (SSR) dist/your-app-name/server/server.mjs

Performance: .htaccess With Caching + Compression

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . /index.html [L]

# GZIP compression
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/css
  AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>

# Cache hashed JS/CSS forever
<FilesMatch "\.(js|css)$">
  Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

# Cache images 1 year
<FilesMatch "\.(jpg|jpeg|png|gif|ico|svg|webp|woff2)$">
  Header set Cache-Control "public, max-age=31536000"
</FilesMatch>

# Always revalidate HTML
<FilesMatch "\.html$">
  Header set Cache-Control "public, max-age=3600, must-revalidate"
</FilesMatch>
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

404 on all routes except /
.htaccess missing. Go to File Manager → Settings → enable Show Hidden Files → create it.

App looks unstyled (no CSS/images)
→ Wrong baseHref in angular.json. Set "baseHref": "/" and rebuild.

cPanel shows "Application is down"
→ Check Detected Configuration Files in the Node.js App panel for error output. Common causes:

  • Wrong startup file path (check the table above)
  • Wrong Node.js version (stop → change → NPM Install → start)
  • Missing dependencies (stop → NPM Install → start)

npm install fails on server
→ Local Node.js doesn't match cPanel. Use nvm use 20 locally, rebuild, re-upload.

Routes break on page refresh (SSR)
→ Missing catch-all route in your server file:

app.get('*', (req, res) => {
  res.sendFile(join(__dirname, '../browser/index.html'));
});
Enter fullscreen mode Exit fullscreen mode

app.server.module not found (Angular 16)
@nguniversal not installed:

npm install @nguniversal/express-engine
npm run build:ssr
Enter fullscreen mode Exit fullscreen mode

Version Comparison

Feature Angular 16 Angular 17 Angular 18 Angular 19
Min Node.js 16.14 18.13 18.19 20.11
SSR package @nguniversal @angular/ssr @angular/ssr Built-in
Build command npm run build:ssr ng build ng build ng build
Startup file server/main.js server/server.mjs server/server.mjs server/server.mjs
server.ts change needed ✅ Yes ❌ No ❌ No ❌ No
ES Modules default ❌ No ❌ No Optional ✅ Yes

Pre-Launch Checklist

  • [ ] Local node --version matches cPanel
  • [ ] ng build completes with zero errors
  • [ ] Uploaded correct folder (browser/ for Angular 17+, root dist for Angular 16 CSR)
  • [ ] .htaccess in domain root (CSR deployments)
  • [ ] node_modules/ NOT uploaded — ran NPM Install in cPanel instead
  • [ ] Startup file path matches your version (see table above)
  • [ ] All routes work including direct URL access
  • [ ] Styles and images load
  • [ ] HTTPS enabled (free Let's Encrypt in cPanel)

Found this useful? The full guide with every detail, decision tree, FAQ, and subfolder configs is at saurabhsharma.dev.

Drop a comment with your Angular version and deployment type — happy to help debug.

Top comments (0)