Featured image of post Auto Deploy Laravel to Cpanel With Github Actions

Auto Deploy Laravel to Cpanel With Github Actions

Set up once, and every push to main triggers automatic deployment — no more FTP, no more manual cPanel logins.

Shared hosting with cPanel is still a popular choice for many developers — it’s affordable and easy to manage. The catch? Deploying is usually a manual process: upload files via FTP or File Manager one by one, then run migrations yourself.

With GitHub Actions, you can automate all of that. Every time you push to main, your server pulls the latest code, installs dependencies, builds assets, and runs migrations — all hands-free.

Also check out my previous article: How to Deploy Laravel to cPanel Hosting


TL;DR

  • Clone your repo straight into the public_html folder
  • Add an .htaccess at the project root to redirect traffic to public/
  • Install Node.js via NVM in cPanel Terminal
  • Generate an SSH key on the server, store the private key in GitHub Secrets
  • Create .github/workflows/deploy.yml, push, and you’re done

Step 1: Clone Your Project into public_html

Open cPanel → Terminal and clone your repo directly into public_html. Since public_html is your domain’s default web root, we’ll use it as the Laravel project root.

1
2
3
4
cd ~
rm -rf public_html
git clone https://github.com/username/your-repo.git public_html
cd ~/public_html

Tip: Back up anything important inside public_html first before wiping it with rm -rf.


Step 1.1: Extra Setup for Private Repos

If your repo is private, cloning via HTTPS will prompt for authentication. The cleanest way to handle this on the server is with an SSH deploy key.

Generate an SSH key in cPanel Terminal:

1
ssh-keygen -t ed25519 -C "cpanel-deploy-key" -f ~/.ssh/deploy_key

Hit Enter when asked for a passphrase (leave it empty).

Display the public key:

1
cat ~/.ssh/deploy_key.pub

Copy the entire output.

Add it to GitHub:

  1. Go to your repo on GitHub → Settings → Deploy keys
  2. Click Add deploy key
  3. Fill in the Title (e.g., cpanel-production)
  4. Paste the public key into the Key field
  5. Check Allow write access if needed (usually not for deployment)
  6. Click Add key

Set up SSH config so Git knows which key to use:

Create ~/.ssh/config:

1
2
3
Host github.com
  IdentityFile ~/.ssh/deploy_key
  IdentitiesOnly yes
1
chmod 600 ~/.ssh/config

Now clone using the SSH URL instead of HTTPS:

1
2
3
cd ~
rm -rf public_html
git clone [email protected]:username/your-repo.git public_html

Tip: You’ll find the SSH URL on your GitHub repo page — click the Code button, then the SSH tab.


Step 2: Set Up .htaccess to Redirect to public/

Laravel serves requests from the public/ subfolder, not the project root. Create an .htaccess file at the root of public_html so all traffic gets routed there — without /public/ showing up in the URL.

~/public_html/.htaccess:

1
2
3
4
5
6
7
Options -MultiViews -Indexes
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^(.*)$ /public/$1 [L,QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^public/(.*)$ /public/$1 [L,QSA]

Step 3: Install Dependencies and Configure .env

Install Composer dependencies, set up your .env file, generate the application key, and run migrations.

1
2
3
4
cd ~/public_html
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
cp .env.example .env
nano .env

Fill in the database section of .env with your cPanel credentials. A small gotcha I’ve hit before: database names and usernames in cPanel always include your cPanel account prefix (e.g., myaccount_laraveldb).

1
2
3
php artisan key:generate
php artisan migrate --force
chmod -R 775 storage bootstrap/cache

Step 4: Install Node.js via NVM

Since we’re using Vite to build assets, we need Node.js on the server. The safest way on shared hosting is through NVM.

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

That version is the latest at the time of writing. For the most recent version, check the nvm-sh/nvm docs.

After installation, load NVM into your shell and install Node.js LTS:

1
2
3
4
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install --lts
nvm alias default 20

To make sure NVM stays loaded when GitHub Actions connects via SSH, add those same lines to ~/.bashrc and source ~/.bashrc inside ~/.bash_profile.


Step 5: Generate an SSH Key and Store It in GitHub Secrets

GitHub Actions needs SSH access to your server. Generate a key pair in cPanel Terminal:

1
2
3
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions
cat ~/.ssh/github_actions.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

If you already created a deploy key in Step 1.1, you can reuse that one instead of generating a new pair.

Display the private key and copy the entire thing — including the -----BEGIN----- and -----END----- lines:

1
cat ~/.ssh/github_actions

Go to your GitHub repo → Settings → Secrets and variables → Actions and add these five secrets:

Secret Value
SSH_HOST Your server’s IP or hostname
SSH_USERNAME Your cPanel username
SSH_KEY The private key from the step above
SSH_PORT 22
PROJECT_PATH /home/cpaneluser/public_html

Step 6: Create the GitHub Actions Workflow

In your local project, create the workflow file:

1
mkdir -p .github/workflows

.github/workflows/deploy.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
name: Deploy via remote SSH

on:
  push:
    branches: [ "main" ]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Execute remote SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            cd ${{ secrets.PROJECT_PATH }}
            git pull origin main
            composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
            npm ci
            npm run build
            php artisan migrate --force
            php artisan optimize:clear
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
            php artisan event:cache
            echo "Deployment completed successfully!"            

A quick breakdown of what the workflow does on every push to main:

  • Pulls the latest code
  • Installs Composer and NPM dependencies
  • Builds front-end assets with Vite
  • Runs database migrations
  • Clears and recaches Laravel’s config, routes, views, and events

The workflow_dispatch trigger means you can also run it manually from the Actions tab whenever you need to.


Step 7: Push and Verify

Commit the workflow and push to main:

1
2
3
git add .github/workflows/deploy.yml
git commit -m "Add GitHub Actions auto-deploy workflow"
git push origin main

This push itself will trigger your very first deployment. Head over to the Actions tab on GitHub and watch each step run — logs appear in real time.

If every step comes back green, open your domain and verify everything’s working. From now on, git push origin main = automatic deploy to your server.


Quick Troubleshooting

Here are a few issues I’ve run into with this setup:

  • npm: command not found — NVM isn’t being loaded in the SSH session. Add the export NVM_DIR and source lines at the top of the script: block in your workflow.

  • Permission denied (publickey) — Double-check chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys on the server.

  • 404 or directory listing — Your .htaccess in ~/public_html is either missing or incorrect. Revisit Step 2.

  • Migration fails — Check the database name and username prefix in your .env file. Remember, cPanel prepends your account name to both.

If you hit any other snags, drop a comment or reach out — I’m happy to help :)


This setup takes a bit of work up front, but after that your deploy workflow is clean as can be. No more manual uploads, no more forgetting to run migrations. Everything runs automatically every time you push.

Happy coding!

Built with Hugo
Theme Stack designed by Jimmy