Next.js + Node.js API Deployment Guide for cPanel/WHM VPS

Complete guide for deploying a Next.js frontend and Node.js API backend to a VPS with WHM+cPanel.

Prerequisites


Phase 1: Server Setup

1.1 Install Node.js and PM2

# SSH as root
ssh root@your-server.com

# Install Node.js 20 (if not installed)
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
yum install -y nodejs

# Verify installation
node --version  # Should be v20.x.x
npm --version

# Install PM2 globally
npm install -g pm2

# Verify PM2
pm2 --version

1.2 Install PostgreSQL (if needed)

# Install PostgreSQL
yum install -y postgresql-server postgresql-contrib

# Initialize database
postgresql-setup --initdb

# Start PostgreSQL
systemctl start postgresql
systemctl enable postgresql

# Verify PostgreSQL is running
systemctl status postgresql

Phase 2: Database Configuration

2.1 Create Database and User

# Switch to postgres user
su - postgres

# Access PostgreSQL
psql

# Create database
CREATE DATABASE your_db_name;

# Create user with password
CREATE USER your_db_user WITH PASSWORD 'your_secure_password';

# Grant privileges
GRANT ALL PRIVILEGES ON DATABASE your_db_name TO your_db_user;
ALTER DATABASE your_db_name OWNER TO your_db_user;

# Exit psql
\q

# Exit postgres user
exit

2.2 Configure PostgreSQL Authentication

# Find PostgreSQL data directory
sudo -u postgres psql -c "SHOW data_directory;"

# Edit pg_hba.conf (path from above command)
nano /var/lib/pgsql/data/pg_hba.conf

Add these lines before the generic ident rules:

# Your application
host    your_db_name    your_db_user    127.0.0.1/32    md5
host    your_db_name    your_db_user    ::1/128         md5

Restart PostgreSQL:

systemctl restart postgresql

2.3 Test Database Connection

# Test connection (will prompt for password)
psql -U your_db_user -d your_db_name -h localhost -W

Phase 3: Upload and Configure Application

3.1 Upload Code to Server

# As root, switch to cPanel user
su - cpanel_username

# Navigate to home directory
cd ~

# Upload your code (via git, scp, or FTP)
# Example with git:
git clone https://github.com/yourusername/your-repo.git
cd your-repo

3.2 Create Production .env File

# Create .env file
nano .env

Add your production environment variables:

# Database
DATABASE_URL="postgresql://your_db_user:URL_ENCODED_PASSWORD@localhost:5432/your_db_name"

# Application URLs
APP_URL=https://app.yourdomain.com
API_URL=https://api.yourdomain.com
NEXT_PUBLIC_APP_URL=https://app.yourdomain.com

# NextAuth
NEXTAUTH_URL=https://app.yourdomain.com
NEXTAUTH_SECRET=your_nextauth_secret

# Other secrets
JWT_SECRET=your_jwt_secret
SESSION_SECRET=your_session_secret

# Add all other required environment variables
Important: URL-encode special characters in passwords:

3.3 Install Dependencies

# Install npm dependencies
npm install

Phase 4: Database Migrations

4.1 Run Prisma Migrations

# Generate Prisma client
npx prisma generate --schema=./db/schema.prisma

# Run migrations
npx prisma migrate deploy --schema=./db/schema.prisma

# Verify migrations
npx prisma migrate status --schema=./db/schema.prisma

Phase 5: Build Applications

5.1 Build Frontend (Next.js)

cd apps/web
npm run build
cd ../..

5.2 Build Backend (Node.js API)

cd apps/api
npm run build
cd ../..

Verify builds exist:

ls -la apps/web/.next/
ls -la apps/api/dist/

Phase 6: PM2 Configuration

6.1 Create PM2 Ecosystem File

# Create ecosystem.config.js in project root
nano ecosystem.config.js

Add PM2 configuration:

module.exports = {
  apps: [
    {
      name: 'your-api',
      script: './apps/api/dist/index.js',
      cwd: '/home/cpanel_username/your-project',
      instances: 1,
      autorestart: true,
      watch: false,
      max_memory_restart: '1G',
      env: {
        NODE_ENV: 'production',
        PORT: 4000
      }
    },
    {
      name: 'your-web',
      script: 'node_modules/.bin/next',
      args: 'start -p 3000',
      cwd: '/home/cpanel_username/your-project/apps/web',
      instances: 1,
      autorestart: true,
      watch: false,
      max_memory_restart: '1G',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      }
    }
  ]
};

6.2 Start PM2 Apps

# Start applications
pm2 start ecosystem.config.js

# Save PM2 process list
pm2 save

# Setup PM2 to start on system boot
pm2 startup

# Copy and run the command PM2 outputs
# Example: sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u cpanel_username --hp /home/cpanel_username

# Verify apps are running
pm2 status

# Check logs
pm2 logs

6.3 Test Local Connectivity

# Test if apps respond locally
curl -I http://localhost:3000
curl -I http://localhost:4000

Phase 7: Create Subdomains in cPanel

7.1 Create Subdomains

  1. Login to cPanel
  2. Go to DomainsSubdomains
  3. Create two subdomains:
    • Subdomain: app → Full domain: app.yourdomain.com
    • Subdomain: api → Full domain: api.yourdomain.com
  4. Note the document root paths (usually /home/cpanel_username/public_html/app.yourdomain.com)

7.2 Install SSL Certificates

  1. Go to SecuritySSL/TLS Status
  2. Select both subdomains:
    • app.yourdomain.com
    • api.yourdomain.com
  3. Click Run AutoSSL
  4. Wait for certificates to be issued (~2-3 minutes)

Phase 8: Configure Apache Reverse Proxy

8.1 Create Apache Include Directories

# As root
mkdir -p /etc/apache2/conf.d/userdata/std/2_4/cpanel_username/app.yourdomain.com
mkdir -p /etc/apache2/conf.d/userdata/std/2_4/cpanel_username/api.yourdomain.com
mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/app.yourdomain.com
mkdir -p /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/api.yourdomain.com

8.2 Create Proxy Configuration for Frontend (HTTP)

cat > /etc/apache2/conf.d/userdata/std/2_4/cpanel_username/app.yourdomain.com/proxy.conf << 'EOF'
ProxyPreserveHost On
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/

RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*)           ws://localhost:3000/$1 [P,L]
EOF

8.3 Create Proxy Configuration for Frontend (HTTPS)

cat > /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/app.yourdomain.com/proxy.conf << 'EOF'
ProxyPreserveHost On
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/

RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*)           ws://localhost:3000/$1 [P,L]
EOF

8.4 Create Proxy Configuration for Backend (HTTP)

cat > /etc/apache2/conf.d/userdata/std/2_4/cpanel_username/api.yourdomain.com/proxy.conf << 'EOF'
ProxyPreserveHost On
ProxyPass / http://localhost:4000/
ProxyPassReverse / http://localhost:4000/
EOF

8.5 Create Proxy Configuration for Backend (HTTPS)

cat > /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/api.yourdomain.com/proxy.conf << 'EOF'
ProxyPreserveHost On
ProxyPass / http://localhost:4000/
ProxyPassReverse / http://localhost:4000/
EOF

8.6 Rebuild Apache Configuration

# Rebuild Apache config to include new files
/usr/local/cpanel/scripts/rebuildhttpdconf

# Restart Apache
systemctl restart httpd

# Verify Apache is running
systemctl status httpd

Phase 9: Verify Deployment

9.1 Test in Browser

Visit these URLs in your browser:

9.2 Verify PM2 Status

# As cPanel user
su - cpanel_username

# Check PM2 status
pm2 status

# View logs
pm2 logs --lines 50

9.3 Test API Endpoints

# Test API health
curl https://api.yourdomain.com/health

# Test frontend
curl -I https://app.yourdomain.com

Phase 10: Main Domain Redirect (Optional)

10.1 Setup Redirect via cPanel

  1. Login to cPanel
  2. Go to DomainsRedirects
  3. Create redirect:
    • Type: Permanent (301)
    • https?://(www.)?: yourdomain.com
    • Redirects to: https://app.yourdomain.com
    • www. redirection: Redirect with or without www.
  4. Click Add

Common Issues and Troubleshooting

Issue: PM2 Apps Keep Stopping/Crashing

Root Cause: PM2 not configured as system service, causing daemon restarts

Permanent Fix:

# As cPanel user, run PM2 startup
cd ~/your-project
pm2 startup

# Copy the command PM2 outputs and run it as ROOT
# Example: sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u cpanel_username --hp /home/cpanel_username

# After running startup command, save PM2 process list
pm2 save

# Verify apps are running
pm2 status

Quick Recovery Command (when apps crash):

cd ~/your-project && pm2 restart all

Check Why Apps Crashed:

# View error logs
pm2 logs --lines 100 --err

# Check memory usage
pm2 monit

Issue: 503 Service Unavailable

Cause: PM2 apps not running or Apache can't connect

Solution:

# Check PM2 status
pm2 status

# Restart apps if stopped
cd ~/your-project
pm2 start ecosystem.config.js

# Test local connectivity
curl -I http://localhost:3000
curl -I http://localhost:4000

Issue: 404 Not Found

Cause: Apache proxy not configured correctly

Solution:

# Verify proxy config files exist
ls -la /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/app.yourdomain.com/
ls -la /etc/apache2/conf.d/userdata/ssl/2_4/cpanel_username/api.yourdomain.com/

# Rebuild and restart Apache
/usr/local/cpanel/scripts/rebuildhttpdconf
systemctl restart httpd

Issue: Database Connection Errors

Cause: PostgreSQL authentication or wrong credentials

Solution:

# Check pg_hba.conf has md5 auth for your user
cat /var/lib/pgsql/data/pg_hba.conf | grep -v "^#" | grep -v "^$"

# Test database connection
psql -U your_db_user -d your_db_name -h localhost -W

# Verify DATABASE_URL in .env has URL-encoded password
grep DATABASE_URL ~/your-project/.env

Issue: SSL Certificate Not Working

Solution:

# Re-run AutoSSL in cPanel
# Or manually via command line:
/usr/local/cpanel/bin/autossl_check --all --verbose

Maintenance Commands

PM2 Management

# View status
pm2 status

# View logs (all apps)
pm2 logs

# View logs (specific app)
pm2 logs your-web
pm2 logs your-api

# Restart apps
pm2 restart all
pm2 restart your-web
pm2 restart your-api

# Stop apps
pm2 stop all

# Delete apps from PM2
pm2 delete all

Apache Management

# Restart Apache
systemctl restart httpd

# View Apache status
systemctl status httpd

# View Apache error logs
tail -f /etc/apache2/logs/error_log

# View domain-specific logs
tail -f /etc/apache2/logs/domlogs/app.yourdomain.com

Database Management

# Access database
psql -U your_db_user -d your_db_name -h localhost

# Run new migrations
cd ~/your-project
npx prisma migrate deploy --schema=./db/schema.prisma

# Reset database (CAUTION: Deletes all data)
npx prisma migrate reset --schema=./db/schema.prisma

Deployment Checklist

Use this checklist for future deployments:


Architecture Summary

┌─────────────────────────────────────────────────────────────┐ │ Internet │ └──────────────────┬──────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Apache (Ports 80/443) │ │ - app.yourdomain.com SSL │ │ - api.yourdomain.com SSL │ └───────┬──────────────────────────┬──────────────────────────┘ │ │ │ Proxy Pass │ Proxy Pass │ │ ▼ ▼ ┌────────────────────┐ ┌─────────────────────┐ │ Next.js App │ │ Node.js API │ │ (PM2 Process) │ │ (PM2 Process) │ │ Port 3000 │ │ Port 4000 │ └────────────────────┘ └──────────┬──────────┘ │ ▼ ┌─────────────────────┐ │ PostgreSQL │ │ Port 5432 │ └─────────────────────┘

Security Best Practices

  1. Use strong passwords for database users
  2. URL-encode special characters in connection strings
  3. Never commit .env files to version control
  4. Keep Node.js and npm updated
  5. Regularly update dependencies (npm audit fix)
  6. Use HTTPS only (SSL certificates)
  7. Restrict database access to localhost only
  8. Set appropriate file permissions:
    chmod 600 .env
    chmod 755 apps/
  9. Monitor PM2 logs regularly for errors
  10. Set up firewall rules to allow only necessary ports

Performance Optimization

1. Enable PM2 Clustering

For better performance, increase instances in ecosystem.config.js:

instances: 'max', // Use all available CPU cores
exec_mode: 'cluster'

2. Enable Apache Compression

# Check if mod_deflate is enabled
apachectl -M | grep deflate

3. Configure Caching Headers

Add to Apache proxy configs:

<FilesMatch "\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$">
    ExpiresActive On
    ExpiresDefault "access plus 1 year"
</FilesMatch>

Backup Strategy

Database Backups

# Create backup script
cat > ~/backup-db.sh << 'EOF'
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
pg_dump -U your_db_user -h localhost your_db_name > ~/backups/db_backup_$DATE.sql
# Keep only last 7 days
find ~/backups/ -name "db_backup_*.sql" -mtime +7 -delete
EOF

chmod +x ~/backup-db.sh

# Setup cron job (daily at 2 AM)
crontab -e
# Add: 0 2 * * * /home/cpanel_username/backup-db.sh

Code Backups

Use git for version control and regular commits.


Support Resources