Hostomy Logo
Hostomy Blog

How to Deploy a Website from GitHub to cPanel Using GitHub Actions

Oct 24, 2026

•

By Alex Grant

Alex Grant
How to Deploy a Website from GitHub to cPanel Using GitHub Actions

If you develop websites, you probably know how repetitive manual deployment can become.

You make a change to your website, build the project, open cPanel File Manager, upload the updated files, and check whether everything works.

Repeat this process every time you update the website.

While this workflow works for small projects, it becomes inefficient as your website grows.

A better approach is to automate deployment.

With GitHub Actions, you can configure a workflow that automatically builds your website and uploads the generated files to your cPanel hosting account whenever you push changes to a GitHub repository.

This means you can focus on development while GitHub handles the deployment process.

In this tutorial, we'll create a deployment workflow using GitHub Actions and SSH. The workflow will build a static website and publish its files to your cPanel document root.

By the end, you'll have a simple continuous deployment pipeline for your website.

What we'll build

The deployment process will look like this:

Developer
   |
   | Push changes to GitHub
   v
GitHub Repository
   |
   | GitHub Actions starts
   v
Build Website
   |
   | Generate production files
   v
SSH Deployment
   |
   | Upload and extract archive
   v
cPanel Hosting
   |
   v
Live Website

This approach works well for static websites built using tools such as Vite, Astro, or similar frameworks.

It can also work for simple PHP websites, provided you deploy the correct application files.

Important: This tutorial is for websites that can be built into static files. Applications requiring a persistent Node.js server, server-side rendering, or background processes need a different deployment setup.


Prerequisites

Before starting, make sure you have the following:

GitHub account: Stores your website's source code

GitHub repository: Contains the website project

cPanel hosting account: Hosts the production website

SSH access: Allows automated deployment

SSH key pair: Authenticates GitHub Actions

Node.js and npm: Builds the example website

Domain name: Points visitors to your website

You also need a cPanel hosting account that supports SSH access.

Not all hosting providers enable SSH on every plan. cPanel documents that shell access can be enabled or disabled by the hosting provider. cPanel & WHM Documentation

If SSH access is unavailable, this particular workflow will not work until your hosting provider enables it.


Step 1: Prepare Your Website for Deployment

For this tutorial, we'll assume you have a website built using a Node.js-based frontend framework.

For example, your project might look like this:

my-website/
├── src/
├── public/
├── package.json
├── package-lock.json
├── vite.config.js
└── index.html

The project should contain a build command in its package.json.

For a typical Vite project, the script looks like this:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

The exact configuration may differ depending on your framework.

Build your website locally

Open your terminal in the project directory.

Run:

npm ci

This installs the project's dependencies using the lockfile.

Next, build the website:

npm run build

For a typical Vite project, the generated production files are stored in a directory named:

dist/

The dist directory contains the files that need to be deployed.

For example:

dist/
├── index.html
├── assets/
│   ├── index-abc123.js
│   └── index-def456.css
└── favicon.ico

The source code itself does not need to be uploaded to the public website directory.

If you're using another framework, check its build output directory. Astro and other static site generators may have their own output configuration.


Step 2: Enable SSH Access in cPanel

Log in to your cPanel account.

Look for:

Security → SSH Access

Depending on your hosting provider, the interface may be slightly different.

If SSH access is disabled, contact your hosting provider and request access.

You'll need the following information:

  • SSH hostname
  • SSH port
  • cPanel username
  • Website document root

For example:

SSH Host: server.example.com
SSH Port: 2222
Username: mycpaneluser
Document Root: /home/mycpaneluser/public_html

These are example values. Use the actual values provided by your hosting company.

Find your document root

The document root is the directory from which your website's files are served.

For a primary domain, it is commonly:

public_html/

For an addon domain or subdomain, the document root may be different.

You can usually find the correct path in cPanel's Domains interface.

For this tutorial, we'll use:

/home/mycpaneluser/public_html

Replace this with your actual document root.


Step 3: Create an SSH Deployment Key

GitHub Actions needs a secure way to authenticate with your hosting account.

We'll use SSH key authentication instead of storing your cPanel password.

Open a terminal on your local computer.

Run:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./hostomy-deploy-key -N ""

This creates two files:

hostomy-deploy-key
hostomy-deploy-key.pub

The private key must remain secret.

The public key can be added to your hosting account.

Import the public key into cPanel

  1. Log in to cPanel.
  2. Open Security → SSH Access.
  3. Click Manage SSH Keys.
  4. Select Import Key.
  5. Enter a name such as github-actions-deploy.
  6. Paste the contents of hostomy-deploy-key.pub into the public key field.
  7. Import the key.

Now locate the imported key in the Public Keys list.

Click Manage and select Authorize.

This step is essential. cPanel requires newly imported public keys to be authorized before they can authenticate SSH connections. cPanel & WHM Documentation

Test SSH access

Before configuring GitHub Actions, test the connection from your local computer.

Run:

ssh -p 2222 \
  -i ./hostomy-deploy-key \
  [email protected]

Replace the hostname, port, username, and key path with your actual values.

If the connection succeeds, you should be able to access your hosting account's shell.

If it fails, resolve the SSH issue before continuing.


Step 4: Configure GitHub Repository Secrets

We should never place SSH credentials directly inside a GitHub Actions workflow.

Instead, we'll use GitHub Actions secrets.

GitHub provides repository-level secrets that workflows can access when explicitly configured. GitHub Docs

Open your GitHub repository.

Navigate to:

Settings → Secrets and variables → Actions

Click New repository secret.

Create the following secrets.

DEPLOY_HOST=server.example.com

DEPLOY_PORT=2222D

EPLOY_USER=mycpaneluser

DEPLOY_PATH=/home/mycpaneluser/public_html

DEPLOY_TMP=/home/mycpaneluser/tmp/hostomy-deploy

DEPLOY_SSH_PRIVATE_KEY=Contents of the private SSH key

DEPLOY_KNOWN_HOSTS=Verified SSH host key entry

Add the private key

Open the private key file:

cat hostomy-deploy-key

Copy its entire contents.

Paste them into the GitHub secret:

DEPLOY_SSH_PRIVATE_KEY

Do not commit this file to your repository.

Do not paste it into your website's source code.

Do not print it in GitHub Actions logs.

Configure SSH host verification

SSH host verification helps ensure that GitHub Actions connects to the intended server.

You can obtain the server's SSH host key using:

ssh-keyscan -p 2222 -t ed25519 server.example.com

However, do not blindly trust the output of ssh-keyscan.

Verify the host key fingerprint against information supplied by your hosting provider or another trusted source before using it.

After verification, store the host key entry in:

DEPLOY_KNOWN_HOSTS

For a non-standard SSH port, the known-hosts entry normally includes the hostname and port in bracket notation.

For example:

[server.example.com]:2222 ssh-ed25519 AAAA...

The example key above is illustrative, not a real server key.


Step 5: Create the GitHub Actions Deployment Workflow

Now we'll create the automation that builds and deploys the website.

In your GitHub repository, create the following file:

.github/workflows/deploy.yml

Add this workflow:

name: Deploy Website to cPanel

on:
  push:
    branches:
      - main

permissions:
  contents: read

concurrency:
  group: production-deployment
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build website
        run: npm run build

      - name: Package website
        run: tar -czf site.tar.gz -C dist .

      - name: Configure SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          SSH_HOST_KEY: ${{ secrets.DEPLOY_KNOWN_HOSTS }}

        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh

          printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519

          printf '%s\n' "$SSH_HOST_KEY" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/known_hosts

      - name: Upload and deploy website
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_TMP: ${{ secrets.DEPLOY_TMP }}

        run: |
          ssh \
            -p "$DEPLOY_PORT" \
            -i ~/.ssh/id_ed25519 \
            -o IdentitiesOnly=yes \
            -o StrictHostKeyChecking=yes \
            "$DEPLOY_USER@$DEPLOY_HOST" \
            "mkdir -p '$DEPLOY_TMP' '$DEPLOY_PATH'"

          scp \
            -P "$DEPLOY_PORT" \
            -i ~/.ssh/id_ed25519 \
            -o IdentitiesOnly=yes \
            -o StrictHostKeyChecking=yes \
            site.tar.gz \
            "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_TMP/site.tar.gz"

          ssh \
            -p "$DEPLOY_PORT" \
            -i ~/.ssh/id_ed25519 \
            -o IdentitiesOnly=yes \
            -o StrictHostKeyChecking=yes \
            "$DEPLOY_USER@$DEPLOY_HOST" \
            "tar -xzf '$DEPLOY_TMP/site.tar.gz' -C '$DEPLOY_PATH' && rm -f '$DEPLOY_TMP/site.tar.gz'"

How this workflow works

Let's break down the important parts.

1. Trigger

on:
  push:
    branches:
      - main

The workflow runs whenever changes are pushed to the main branch.

GitHub Actions supports branch-specific workflow triggers through the push event. GitHub Docs

2. Build

- name: Build website
  run: npm run build

This generates the production version of the website.

3. Package

- name: Package website
  run: tar -czf site.tar.gz -C dist .

This creates a compressed archive containing the generated website files.

The archive is easier to transfer than uploading many individual files.

4. SSH configuration

The workflow creates a private key file and configures SSH to verify the server's host key.

5. Upload

The scp command transfers the archive to the cPanel account.

6. Deployment

The final SSH command extracts the archive into the website's document root.

The archive is removed after extraction.

Important security note

The workflow uses version tags for readability.

For production environments, review the source of each GitHub Action and consider pinning actions to full commit SHAs. GitHub recommends SHA pinning as a way to reduce risks associated with mutable action tags. GitHub

Also, verify that your selected Node.js version is compatible with your project's dependencies.


Step 6: Deploy Your First Website

Now that the workflow is configured, let's test it.

First, make sure your repository contains:

.github/
└── workflows/
    └── deploy.yml

Your project should also contain:

package.json
package-lock.json

And your website should build successfully.

Push the workflow to GitHub

Run:

git add .
git commit -m "Add automated cPanel deployment"
git push origin main

Open your GitHub repository.

Navigate to:

Actions

You should see a workflow named:

Deploy Website to cPanel

Click the workflow run.

If everything is configured correctly, the workflow will:

  1. Check out your repository.
  2. Install dependencies.
  3. Build the website.
  4. Package the generated files.
  5. Connect to your cPanel account.
  6. Upload the archive.
  7. Extract the website files.

Once the workflow completes successfully, open your website in a browser.

Your website should now display the latest version.

Test automatic deployment

Make a small change to your website.

For example, update the homepage heading:

<h1>Welcome to My Website</h1>

Change it to:

<h1>Welcome to My Updated Website</h1>

Commit and push the change:

git add .
git commit -m "Update homepage heading"
git push origin main

GitHub Actions should automatically deploy the updated website.

You no longer need to manually upload the generated files through cPanel File Manager.


Step 7: Troubleshoot Common Deployment Problems

Automated deployments can fail for several reasons.

Here are some common issues and how to resolve them.

Error: Permission denied (publickey)

This usually means SSH authentication failed.

Possible causes:

  • The public key was not imported into cPanel.
  • The key was imported but not authorized.
  • The wrong private key is stored in GitHub.
  • The username is incorrect.

Solution:

Verify that the public key is authorized in cPanel.

Check that DEPLOY_SSH_PRIVATE_KEY contains the correct private key.

Also confirm that the SSH username matches your cPanel account.

Error: Host key verification failed

This means SSH could not verify the server's identity.

Possible causes:

  • The known-hosts entry is incorrect.
  • The SSH port is wrong.
  • The server's host key has changed.
  • The host key was obtained from an untrusted source.

Solution:

Verify the server's actual host key fingerprint with your hosting provider.

Update DEPLOY_KNOWN_HOSTS only after verifying the new key.

Do not disable host key verification to make the deployment work.

Error: Connection timed out

This usually indicates a network or SSH configuration issue.

Check:

  • Is SSH enabled on your hosting account?
  • Are you using the correct SSH port?
  • Is your hosting provider blocking external SSH connections?
  • Does the server allow connections from GitHub-hosted runners?

If your hosting provider restricts SSH connections by IP address, you may need to configure an appropriate access policy.

Error: No such file or directory: dist

The workflow expects the website build output to exist in:

dist/

However, some frameworks use different output directories.

For example, a framework might generate:

build/

or:

out/

Solution:

Check your framework's build configuration.

If the output directory is different, update the packaging command accordingly.

For example:

tar -czf site.tar.gz -C build .

Error: Website displays an old version

Possible causes include:

  • Browser caching.
  • CDN caching.
  • Deployment to the wrong document root.
  • The build output was not updated.

Check the deployment logs and confirm the correct directory is being updated.

If you're using a CDN, purge its cache after deployment when necessary.

Error: Old files remain after deployment

The workflow extracts the archive over the existing website directory.

This overwrites matching files, but it does not automatically delete files that are no longer present in the build output.

For example, if your old build contains:

assets/old-script.js

and the new build no longer contains that file, the old file may remain on the server.

This behavior is intentional in the example workflow because automatically deleting production files can be dangerous.

If you need a deployment process that removes obsolete files, consider a carefully tested release-based deployment or a controlled synchronization strategy.

Do not run an unrestricted recursive deletion command against your document root without a verified backup.


Step 8: Secure Your Automated Deployment

Automated deployment introduces a new way to access your hosting account.

It should be configured carefully.

Use SSH keys instead of passwords

SSH key authentication avoids storing your cPanel password in the workflow.

Use a dedicated deployment key and revoke it when it is no longer needed.

Remember that an SSH key authorized for a cPanel account may have access to more than just the website's document root. Review the privileges associated with the account.

Protect your GitHub secrets

Never commit private SSH keys, passwords, or API credentials to your repository.

Use GitHub Actions secrets.

Also avoid exposing deployment secrets in logs.

Restrict deployment to trusted branches

The example workflow deploys only when changes are pushed to main.

This helps prevent accidental deployment from development branches.

Do not expose production secrets to untrusted pull request workflows.

Verify the SSH host key

Always verify the server's identity before allowing GitHub Actions to connect.

Avoid disabling:

StrictHostKeyChecking

A secure deployment should verify both the client's credentials and the server's identity.

Keep backups

Before introducing automated deployment, create a backup of the existing website.

For WordPress or other dynamic applications, remember that a website backup may need both files and a database backup.

A deployment workflow that only copies static files does not provide a complete recovery strategy for a dynamic website.


Frequently Asked Questions

Can I deploy a WordPress website from GitHub to cPanel?

Yes, but the workflow depends on what you are deploying.

If you're deploying a custom WordPress theme, plugin, or static frontend, GitHub Actions can automate the process.

However, a complete WordPress website also requires its database and configuration. Simply uploading a static build will not deploy a complete WordPress installation.

Can I use GitHub Actions with shared hosting?

Yes, provided your hosting account supports a suitable deployment method.

SSH is one option.

Other hosting accounts may support FTP or SFTP deployment, although SSH-based workflows offer additional flexibility.

Does this method require a VPS?

No.

This tutorial uses cPanel shared hosting.

A VPS is not required for a static website that can be built into HTML, CSS, and JavaScript files.

However, applications requiring a persistent server-side runtime may need VPS or application hosting.

Can I deploy automatically whenever I push to GitHub?

Yes.

The workflow is configured to run whenever changes are pushed to the main branch.

You can change the branch name or add additional deployment conditions in the workflow configuration.

Is GitHub Actions free?

GitHub Actions has usage limits and billing rules that depend on your GitHub plan, repository type, and runner configuration.

Check GitHub's current documentation for the applicable limits.


Conclusion

Manually uploading website files to cPanel can become repetitive as your project grows.

By combining GitHub Actions with SSH, you can automate the build and deployment process.

The workflow in this tutorial provides a simple way to:

  • Build your website automatically.
  • Store deployment credentials securely.
  • Upload production files to cPanel.
  • Deploy updates whenever you push changes to GitHub.

This is particularly useful for developers working with static websites, frontend frameworks, and projects hosted on traditional shared hosting.

Once the deployment process is working, you can extend it with testing, staging environments, release management, and more advanced deployment strategies.

Deploy your website with Hostomy

If you're building a website and want a hosting environment that fits your development workflow, explore Hostomy's hosting options.

For a workflow like this, check that your chosen hosting plan supports SSH access and allows the commands required for deployment.

If your application requires a persistent Node.js server, background workers, or other server-side processes, consider a hosting environment designed for those workloads.

Choose the hosting setup that fits your application rather than adding unnecessary infrastructure.