cPanel

How to Deploy a Website to cPanel with Git and GitHub Actions (Beginner's Guide)

Step by step: put your site in Git, push it to a private GitHub repo, and deploy it to cPanel over SFTP with a GitHub Actions workflow and an SSH deploy key.

Uploading a website by hand with FTP works until the day you overwrite the wrong file, forget one, or need yesterday's version back. This guide sets up a better path: your computer → GitHub → your cPanel hosting account. Every change is saved in Git, GitHub keeps a private copy, and one click uploads the site for you. It is written for beginners, and every step says what it does and why. The pitfalls near the end are real ones we hit while moving this very site.

Quick answer

  1. Put your site in a Git repository and push it to a private GitHub repo.
  2. On your computer, make a deploy key with ssh-keygen -t ed25519 -N "".
  3. In cPanel → SSH Access, import the .pub file and click Authorize.
  4. In GitHub → Settings → Secrets and variables → Actions, add the key, server, username, port and folder as Secrets (not Variables).
  5. Add a GitHub Actions workflow that builds the site and uploads it with sftp, set to run only by hand (workflow_dispatch).
  6. Take a cPanel full backup, run the workflow from the Actions tab, then check the live site.

What you need: a cPanel hosting account with SFTP (you do not need shell or terminal access; ask your host which SFTP port they use, often 22 or a custom one), a free GitHub account, and a terminal (Terminal on macOS, PowerShell on Windows).

The big picture: why not just upload by hand?

Your computer  --git push-->  GitHub (private repo)  --Actions: sftp-->  cPanel public_html
  • History. Git records every change. You can see what changed, when, and roll back.
  • Undo. A broken edit is one git revert away, instead of hunting for an old copy.
  • No lost files. GitHub holds a full copy, so a dead laptop does not take the site with it.
  • Repeatable uploads. The same workflow uploads the same way every time, with no forgotten files.

Placeholders used below: example.com for your server, cpuser for your cPanel username, 2222 for your SFTP port, and /home/cpuser/public_html for the website folder. Replace them with your own values, and never post your real ones publicly.

1. Install Git

  • macOS: run git --version in Terminal. If Git is missing, macOS offers to install the Command Line Tools; accept.
  • Windows: install Git for Windows from the Git website, keeping the default options.
  • Linux: use your package manager, for example sudo apt install git or sudo dnf install git.

Then tell Git who you are. This name and email appear on each saved change:

git config --global user.name "Your Name"
git config --global user.email "you@example.com"

2. Make your first repository and commit

Go to your website's folder and turn it into a Git repository:

cd ~/my-website
git init -b main

Some files should never go into Git, such as passwords and build output. List them in a file called .gitignore:

.env
node_modules/
dist/

Now save a first snapshot (a commit). git status shows what will be included; check it before committing:

git add .
git status
git commit -m "First version of the site"

3. Create a private GitHub repository and push

With the GitHub CLI (gh), sign in once, then create the repo and push in one go: gh repo create

gh auth login
gh repo create my-website --private --source=. --push

With the web page: on GitHub click New repository, name it, choose Private, and do not add a README. GitHub then shows the commands to connect your folder; they look like this:

git remote add origin git@github.com:your-account/my-website.git
git push -u origin main

From now on, after each change: git add, git commit, git push. Pushing only stores your work on GitHub; it does not change the live site. That happens in step 7, and only when you ask.

4. Create an SSH deploy key

GitHub needs a way to log in to your hosting account without your cPanel password. A deploy key is a key pair used only for this. If it ever leaks, you delete it in cPanel and your password is untouched.

Make the key on your own computer:

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

This creates two files:

  • ~/.ssh/cpanel_deploy: the private key. It goes into GitHub Secrets and nowhere else.
  • ~/.ssh/cpanel_deploy.pub: the public key. It goes into cPanel.

-N "" means "no passphrase". Normally a passphrase is a good idea, but an automated deploy has nobody to type it. That is also why we do not use cPanel's own Generate a New Key button: it insists on a passphrase.

5. Import and authorize the key in cPanel

You don't need shell (terminal) access for this. SFTP runs through the server's SSH service, but it is only a file-transfer channel: it can upload and download files, not run commands. cPanel keeps keys on a page called SSH Access, yet authorizing a key there does not give anyone a command line. Many hosts leave shell access off for shared accounts and still allow SFTP, so this setup works for almost anyone on cPanel.

  1. Log in to cPanel and open Security → SSH Access → Manage SSH Keys. cPanel SSH Access

  2. Click Import Key. Give it a name such as github-deploy.

  3. Show the public key and copy it (macOS shown; on Windows use Get-Content $HOME\.ssh\cpanel_deploy.pub):

    cat ~/.ssh/cpanel_deploy.pub
    
  4. Paste it into the public key box, leave the private key box empty, and click Import.

  5. Go back to Manage SSH Keys. Under Public Keys, find the key and click Manage, then Authorize.

An imported key does nothing until it is authorized. Forgetting step 5 is the most common reason for Permission denied (publickey).

You can test the key from your computer before involving GitHub:

sftp -P 2222 -i ~/.ssh/cpanel_deploy cpuser@example.com

If you see an sftp> prompt, it works. Type bye to leave.

6. Store the details as GitHub Secrets

In your GitHub repo, open Settings → Secrets and variables → Actions. There are two tabs, Secrets and Variables. Use Secrets: they are encrypted and hidden in logs. Variables are shown in plain text to anyone who can see the repo. Using secrets in GitHub Actions

Click New repository secret and add these five:

Name Value (example)
SFTP_SERVER example.com
SFTP_PORT 2222
SFTP_USERNAME cpuser
SFTP_REMOTE_PATH /home/cpuser/public_html
SFTP_PRIVATE_KEY the whole private key file

The private key must be pasted exactly, including the -----BEGIN and -----END lines. Copy it straight to the clipboard instead of selecting it by hand:

pbcopy < ~/.ssh/cpanel_deploy

On Windows (PowerShell):

Get-Content $HOME\.ssh\cpanel_deploy -Raw | Set-Clipboard

7. Add the deploy workflow

A workflow is a file that tells GitHub what to do. Create .github/workflows/deploy.yml in your repo:

# Manual on purpose: a push must never deploy by accident. Run it from the Actions tab.
name: Deploy to cPanel

on:
  workflow_dispatch:

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

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci

      - run: npm run build

      - name: Upload dist/ over SFTP
        env:
          SFTP_PRIVATE_KEY: ${{ secrets.SFTP_PRIVATE_KEY }}
          SFTP_SERVER: ${{ secrets.SFTP_SERVER }}
          SFTP_PORT: ${{ secrets.SFTP_PORT }}
          SFTP_USERNAME: ${{ secrets.SFTP_USERNAME }}
          SFTP_REMOTE_PATH: ${{ secrets.SFTP_REMOTE_PATH }}
        run: |
          install -m 600 /dev/null ~/deploy_key
          # printf adds back the final newline; without it OpenSSH rejects the key.
          printf '%s\n' "$SFTP_PRIVATE_KEY" > ~/deploy_key
          # Stops here with a clear error if the stored key is damaged.
          ssh-keygen -y -f ~/deploy_key > /dev/null
          # The * glob skips hidden files, so .htaccess gets its own put.
          printf 'cd %s\nlcd dist\nput -r *\nput .htaccess\n' "$SFTP_REMOTE_PATH" > batch
          sftp -P "$SFTP_PORT" -i ~/deploy_key -o StrictHostKeyChecking=accept-new \
               -b batch "$SFTP_USERNAME@$SFTP_SERVER"

      - name: Remove deploy key
        if: always()
        run: rm -f ~/deploy_key

What each part does:

  • workflow_dispatch means the workflow runs only when you click Run. A push never deploys by accident. Manually running a workflow
  • concurrency stops two deploys from running over each other.
  • npm ci and npm run build build the site into dist/. If your site is plain HTML with no build step, delete those three Node steps and change lcd dist to lcd . (or the folder that holds your pages).
  • The upload step writes the key to a file only the runner can read, checks it, then runs sftp with a list of commands (-b batch). sftp manual
  • The last step deletes the key even if the upload failed.

If your site has no .htaccess, remove \nput .htaccess from the printf line, or sftp stops with an error. Commit and push the workflow:

git add .github/workflows/deploy.yml
git commit -m "Add manual deploy workflow"
git push

8. Take a cPanel backup, then deploy

The first upload writes over whatever is in public_html. Before that, take a backup: in cPanel open Files → Backup Wizard → Back Up → Full Backup, and download it when it is ready. cPanel Backup Wizard If anything goes wrong, you can put the old site back.

Now deploy: in GitHub open the Actions tab, choose Deploy to cPanel, click Run workflow, and confirm. Click the run to watch each step. A green tick means the upload finished.

Note that this upload only adds and replaces files. It never deletes anything on the server, so old files you no longer need stay until you remove them in cPanel's File Manager.

9. Check the live site

Do not stop at the green tick. Open the site and check it yourself:

  • Load the home page and a few inner pages in a private browser window, so you are not seeing a cached copy.
  • Open a page that does not exist and check your 404 page appears (this proves .htaccess arrived).
  • In cPanel's File Manager, look in public_html for the new files and their dates.

From the terminal, curl -I shows the server's reply without downloading the page; you want 200:

curl -I https://www.example.com/

Pitfalls we really hit

"error in libcrypto" when connecting. We first used a popular third-party SFTP action. It saved the private key without its final newline, and OpenSSH refused the key with Load key ... error in libcrypto. The fix was the plain sftp step above: printf '%s\n' writes the key with its newline, and ssh-keygen -y checks it before any upload starts.

.htaccess never arrived. put -r * looked fine, but the * pattern skips hidden files, whose names start with a dot. .htaccess was silently left behind, so our custom error pages did not work. The fix is the separate put .htaccess line.

Secrets saved as Variables. The values were added under the Variables tab by mistake. The workflow reads secrets.…, so it got empty values and failed, and worse, the server details were visible in plain text. Delete them from Variables and add them again under Secrets.

No backup before the first deploy. The first deploy replaces the old site's files. Take the cPanel full backup in step 8 first; it costs five minutes and makes every mistake reversible.

Troubleshooting

  • Permission denied (publickey): the key is not authorized in cPanel (step 5), the username is wrong, or the private key secret is incomplete. Run the sftp test from step 5 on your computer to narrow it down.
  • Connection timed out or Connection refused: wrong port or server name, or SFTP is turned off for your account. Ask your host for the SFTP port and to confirm SFTP is allowed (shell access isn't needed).
  • error in libcrypto or invalid format: the private key was pasted incompletely. Copy it again with pbcopy (or the PowerShell command) and update the secret.
  • No such file or directory on cd: SFTP_REMOTE_PATH is wrong. In cPanel's File Manager the full path of your home folder is shown at the top; the site usually lives in public_html inside it.
  • The site looks unchanged: clear your browser cache or try a private window. If you use Cloudflare or another CDN, purge its cache.
  • The workflow is not listed under Actions: the file must be at exactly .github/workflows/deploy.yml on your default branch, and it must be pushed.
  • Need to revoke access: delete the key in cPanel → SSH Access. Nothing else changes, and your cPanel password stays the same.

Sources

  1. Git: Installing Git
  2. GitHub CLI manual: gh repo create
  3. GitHub Docs: Using secrets in GitHub Actions
  4. GitHub Docs: Manually running a workflow
  5. cPanel Documentation: SSH Access
  6. cPanel Documentation: Backup Wizard
  7. OpenBSD manual: sftp(1)

Written by Tharanga. Commands and procedures depend on your system and software version.

How we approach technical guides →
FOLLOW THE THREAD

Keep exploring

All guides
Linux1 min read

How to Check Disk Space in Linux

See how much disk space is free on a Linux server and find the folders using the most space, with two beginner-friendly commands: df and du.