From Git Push to Live Site: Deploying a Next.js App to cPanel Node.js Shared Hosting
14 min read

From Git Push to Live Site: Deploying a Next.js App to cPanel Node.js Shared Hosting

A step-by-step guide for deploying and automating a Next.js or Node.js app on cPanel shared hosting with GitHub Actions, SSH keys, and safe release checks.

Next.jsNode.jscPanelGitHub ActionsDeployment

If you have only deployed Node.js applications on Vercel, Netlify, or your own VPS, cPanel shared hosting can feel strange at first. The good news is that it works fine once you understand the rules.

In this guide, I will show the exact journey for taking a Next.js project, pushing it to GitHub, connecting GitHub Actions to cPanel with an authorized SSH key, and letting every push to main deploy the application automatically.

Workflow diagram showing GitHub Actions deploying a Next.js app to cPanel Git hosting

What we are building

By the end of this tutorial, the deployment flow looks like this:

  1. You keep your Next.js project in Git and GitHub.
  2. GitHub Actions runs a deployment workflow every time you push to main.
  3. GitHub connects to your cPanel server over SSH using a private key stored in GitHub secrets.
  4. The server pulls the latest code, installs dependencies only when needed, builds the app, restarts the Node.js application correctly, and checks that the new assets are really live.

This is not the only way to deploy to cPanel, but it is a solid approach when you want safer automated releases instead of manually uploading files every time.

Before you start

Make sure you already have these items ready:

  1. A cPanel hosting account with Setup Node.js App enabled.
  2. SSH access enabled for that cPanel account.
  3. A Next.js or Node.js project that already runs locally.
  4. A GitHub repository for the project.
  5. A domain or subdomain already pointing to the cPanel account.

If your hosting plan does not support Node.js applications or SSH access, this workflow will not work on that plan.

Step 1: Prepare the project for cPanel

The first important idea is that cPanel should manage the running process. Do not treat shared hosting like a VPS where you manually keep node server.js running in a terminal.

For this project, the custom startup file is server.js:

import { createServer } from 'node:http'
import { parse } from 'node:url'
import next from 'next'

const dev = false
const hostname = '0.0.0.0'
const port = Number.parseInt(process.env.PORT ?? '3000', 10)
const app = next({ dev, hostname, port })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer(async (request, response) => {
    await handle(request, response, parse(request.url, true))
  }).listen(port, hostname)
})

This works well on cPanel because the Node.js app can point to one startup file and let Passenger or LiteSpeed handle the process lifecycle.

You should also make sure your package.json includes clear scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "build:production": "next build --webpack",
    "start": "node server.js"
  }
}

Why use build:production here?

In this setup, the production deployment uses:

npm run build:production

That gives you a single explicit build command for the server workflow. In this repo it uses next build --webpack, which is useful when you want a predictable server build path for hosting environments like cPanel.

Step 2: Add the project to Git and GitHub

If your project is not in Git yet, initialize it:

git init
git add .
git commit -m "Initial commit"

Then create a GitHub repository and connect your local project:

git remote add origin git@github.com:YOUR_USERNAME/YOUR_REPOSITORY.git
git branch -M main
git push -u origin main

At this point, GitHub becomes the source of truth. That matters because we want every server release to come from committed code, not from random manual edits inside cPanel.

Step 3: Create the application in cPanel

Now move to the cPanel side.

Open Setup Node.js App and create a new application. Use values similar to these:

  1. Node.js version: choose the newest stable version your host supports and that matches your project.
  2. Application mode: Production
  3. Application root: the folder where the repository will live, for example repositories/myblog
  4. Application URL: your domain or subdomain
  5. Application startup file: server.js

After creating the app, cPanel usually gives you environment activation instructions similar to:

source /home/USERNAME/nodevenv/repositories/myblog/22/bin/activate
cd /home/USERNAME/repositories/myblog

Save those paths. They are important for your workflow.

Important beginner note

Do not press random buttons like Run JS Script with start if your app is already managed by Setup Node.js App. In this kind of hosting, cPanel owns the Node process. Your deployment should build the app and then let cPanel restart it properly.

Step 4: Put the code on the server with cPanel Git

Inside cPanel, open Git Version Control and create a repository on the server.

For example, this project uses:

/home/trynvtcp/repositories/myblog

You can either clone from GitHub or create the repository path first and connect it later. The main goal is simple: the server must have a Git working tree that GitHub Actions can update.

Once the repository exists on the server, make sure it is on the correct branch:

cd /home/USERNAME/repositories/YOUR_REPOSITORY
git branch

If needed:

git checkout main

Step 5: Create an SSH key for GitHub Actions

This is the part many junior developers find confusing, so let us make it simple.

GitHub Actions needs a secure way to log in to cPanel over SSH. The safest common approach is:

  1. Generate an SSH key pair only for deployment.
  2. Put the private key in GitHub secrets.
  3. Import the public key into cPanel and authorize it.

Generate the key on your machine:

ssh-keygen -t ed25519 -C "github-actions-cpanel-deploy" -f ~/.ssh/cpanel_deploy

This creates:

  1. ~/.ssh/cpanel_deploy as the private key
  2. ~/.ssh/cpanel_deploy.pub as the public key

Step 6: Import and authorize the key in cPanel

In cPanel, open SSH Access and then Manage SSH Keys.

Follow these steps:

  1. Click Import Key.
  2. Paste the public key from cpanel_deploy.pub.
  3. Save it.
  4. Find the imported key in the list.
  5. Click Manage.
  6. Click Authorize.

That authorization step is the missing link for many people. Importing the key is not enough. The key must be authorized before SSH login works.

Step 7: Add the deployment secrets in GitHub

In GitHub, go to:

Repository Settings → Environments → New environment

Create an environment named:

production

Then add these secrets or variables:

  1. CPANEL_HOST
  2. CPANEL_USER
  3. CPANEL_SSH_PRIVATE_KEY
  4. CPANEL_KNOWN_HOSTS

For this project, the deployment documentation uses these meanings:

  1. CPANEL_HOST: your hosting server hostname or domain
  2. CPANEL_USER: your cPanel username
  3. CPANEL_SSH_PRIVATE_KEY: the private key content from cpanel_deploy
  4. CPANEL_KNOWN_HOSTS: the server host key output

To collect known hosts safely, run this on your machine:

ssh-keyscan -p 21098 YOUR_HOSTNAME

Then save the output as CPANEL_KNOWN_HOSTS.

If your host uses a different SSH port, replace 21098 with your actual port.

Step 8: Add the GitHub Actions workflow YAML

Create this file:

.github/workflows/deploy-cpanel.yml

Here is the important structure:

name: Deploy to cPanel

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: cpanel-production-deploy
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    env:
      CPANEL_HOST: ${{ secrets.CPANEL_HOST || vars.CPANEL_HOST }}
      CPANEL_USER: ${{ secrets.CPANEL_USER || vars.CPANEL_USER }}
      CPANEL_KNOWN_HOSTS: ${{ secrets.CPANEL_KNOWN_HOSTS || vars.CPANEL_KNOWN_HOSTS }}

Then add steps to:

  1. Validate that required secrets exist.
  2. Write the SSH key into ~/.ssh.
  3. Connect to cPanel over SSH.
  4. Fast-forward the repository on the server to origin/main.
  5. Install dependencies only when package.json or the lockfile changed.
  6. Build the app.
  7. Restart the app safely.
  8. Verify that the public site is really serving the newest Next.js assets.

Step 9: Understand the remote deployment script

This is the part that actually runs on the server.

In this project, the workflow uses values like these:

APP_ROOT="$HOME/repositories/myblog"
NODE_ENV_ROOT="$HOME/nodevenv/repositories/myblog/22"
SITE_URL="https://tryndev.com"

Those should match your real cPanel paths and domain.

The deployment script then follows this release order:

  1. Stash tracked server-side changes if someone manually edited files on the server.
  2. Fetch origin/main.
  3. Check whether dependency files changed.
  4. Merge with git merge --ff-only origin/main.
  5. Activate the Node environment created by cPanel.
  6. Stop only the current application processes.
  7. Run npm install --include=dev --no-package-lock only when needed.
  8. Preserve the previous .next build as a rollback copy.
  9. Run npm run build:production.
  10. Touch tmp/restart.txt to wake the app.
  11. Request the public site.
  12. Verify that the public HTML is pointing to the same asset hashes as the local Node origin.

This is much safer than a basic deploy script that only does git pull && npm install && npm run build.

Step 10: Why tmp/restart.txt matters

On many cPanel Node.js setups, touching this file tells Passenger or the hosting runtime to recycle the application:

mkdir -p tmp
rm -f tmp/restart.txt
touch tmp/restart.txt

This is one of the most important shared-hosting details to understand.

If you build the app but never trigger a proper restart, the public domain may still serve the old process and old asset references.

Step 11: Verify that the deployment is really live

One of the smartest parts of this deployment flow is the verification step.

It compares the asset URLs from:

  1. The public website
  2. The local Node origin on the server, usually http://127.0.0.1:3000/

Example:

diff \
  <(curl -s http://127.0.0.1:3000/ | grep -oE '/_next/static/[^" ]+\.(css|js)' | sort -u) \
  <(curl -ks https://YOUR_DOMAIN/ | grep -oE '/_next/static/[^" ]+\.(css|js)' | sort -u)

If there is no output, the deployed build is consistent.

If the hashes differ, your public site may still be serving stale HTML or an old LiteSpeed process.

Step 12: Manual recovery when something goes wrong

Even with automation, it is good to know the manual fallback.

The safe manual order is:

  1. Stop the app from Setup Node.js App.
  2. Confirm the old process is really stopped.
  3. Activate the node environment.
  4. Go to the repository folder.
  5. Install dependencies if needed.
  6. Remove .next.
  7. Build again.
  8. Touch tmp/restart.txt.
  9. Start the app again from cPanel.

Example commands:

source /home/USERNAME/nodevenv/repositories/YOUR_REPOSITORY/22/bin/activate
cd /home/USERNAME/repositories/YOUR_REPOSITORY
npm install --include=dev --no-package-lock
rm -rf .next
npm run build:production
mkdir -p tmp
touch tmp/restart.txt

Common mistakes to avoid

Here are the problems I see most often:

1. Running the app manually instead of through cPanel

Avoid using:

npm start
node server.js

for the long-running production process on shared hosting. Build through SSH if needed, but let cPanel own the runtime.

2. Forgetting to authorize the imported SSH key

Importing is not enough. The public key must be authorized in SSH Access.

3. Using the wrong startup file

If cPanel is configured with the wrong startup file, the app may build successfully but fail to run.

4. Using paths copied from a different server

Always confirm the real values for:

  1. repository path
  2. node environment path
  3. domain
  4. SSH port

5. Trusting a deploy without verification

Sometimes the build passes but the public domain still serves old asset hashes. This is why the asset verification step is so valuable.

A practical YAML mindset for junior developers

When you read a deployment workflow, do not think of it as magic. Read it as a checklist:

  1. Can GitHub log in to the server?
  2. Does the server have the newest code?
  3. Are dependencies installed?
  4. Did the build succeed?
  5. Did the application restart?
  6. Is the public site actually serving the new release?
  7. Is there a rollback path if verification fails?

If your YAML answers all seven questions, you already have a much stronger deployment workflow than most beginner setups.

My recommendation

If you are deploying a Next.js project to cPanel shared hosting, I recommend this pattern:

  1. Keep the project in GitHub.
  2. Use a dedicated deployment SSH key.
  3. Authorize the public key in cPanel.
  4. Use GitHub Actions for the release.
  5. Build on the server only after syncing the exact Git commit.
  6. Restart using tmp/restart.txt.
  7. Verify public asset hashes after every deployment.

That gives you a workflow that is still simple enough for a small project, but much safer than manual deployment.

Final thoughts

cPanel shared hosting is not the most modern deployment target for Node.js work, but that does not mean it has to be messy.

Once you connect Git, GitHub, SSH authorization, cPanel Node.js configuration, and a careful YAML deployment script, the process becomes repeatable and much less stressful.

If you are building for clients, side projects, or small business sites on cPanel, this setup is a very practical middle ground between fully manual deployment and expensive infrastructure.