Skip to main content

Git Deploy to Hosting and VPS: Automation via Hooks

Hosting & cPanel · 15.03.2026 · 6 min read
Illustration for “Git Deploy to Hosting and VPS: Automation via Hooks”

Instead of manual FTP on every change — set up git push for automatic deployment. Takes 15 minutes, saves hours of work.

Deploying to a VPS via a Git bare repo

# On the VPS: create a bare repository
mkdir -p /var/repo/mysite.git
cd /var/repo/mysite.git
git init --bare

# Create the post-receive hook
nano hooks/post-receive
#!/bin/bash
GIT_WORK_TREE=/var/www/mysite git checkout -f main
echo "Deployed to /var/www/mysite"
chmod +x hooks/post-receive
# On your local machine:
git remote add production user@server-ip:/var/repo/mysite.git
git push production main

Deploying to cPanel hosting via Git

cPanel → Files → Git™ Version Control:

  1. Create → specify the path to the repository (public_html)
  2. Specify the GitHub/GitLab repository URL
  3. Click Update from Remote for a manual deploy

GitHub Actions: full auto-deploy

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/mysite
            git pull origin main
            composer install --no-dev
            php artisan migrate --force
✅ Keep your SSH key, IP, and passwords in GitHub Secrets (Settings → Secrets → Actions). Never commit .env files.

Common Errors During Git Deployment

Below are the messages you'll actually run into when first setting up a bare repository and hooks, and what's behind them.

Permission denied (publickey).
fatal: Could not read from remote repository.

bash: hooks/post-receive: Permission denied

remote: error: hook declined to update refs/heads/main
error: failed to push some refs to 'server-ip:/var/repo/mysite.git'

fatal: 'production' does not appear to be a git repository
MessageCauseFix
Permission denied (publickey)The client's SSH key isn't added on the server, or the wrong key is being usedCheck with ssh -T user@server-ip -i path_to_key, add the public key to ~/.ssh/authorized_keys
hooks/post-receive: Permission deniedThe hook isn't marked executable — chmod +x was skippedRun chmod +x hooks/post-receive and check the first line of the file (#!/bin/bash)
hook declined to update refs / failed to push some refsThe hook itself exited with a non-zero status — for example, git checkout -f failed due to permissions on the working directoryRun the hook manually: bash hooks/post-receive, fix the owner and permissions on /var/www/mysite
fatal: 'production' does not appear to be a git repositoryWrong remote URL or a typo in the path to the bare repositoryCompare git remote -v with the actual path on the server — the directory must end with .git

Comparing Deployment Methods

MethodSSH requiredDeploy speedWhen to use it
Bare repo + post-receive hookYes, key-based access is requiredInstant after git pushA VPS with full access, your own pipeline with no third-party services
cPanel Git Version ControlNot required, works through the web interfaceManual, via the Update from Remote buttonShared hosting, a developer without SSH access
GitHub ActionsYes, for the SSH connection step to the serverAutomatic on push to main, 1–3 minutesA team that needs tests and a build step before deployment

When Git Deployment Doesn't Fit

  • The site stores user files (media library, uploads) right inside the repository's working directory — git checkout -f during deploy can touch them without a careful .gitignore
  • You need zero-downtime deployment with an instant rollback — plain checkout -f doesn't give you an atomic version swap; that needs a releases-plus-symlink scheme
  • The hosting has neither SSH nor a Git Version Control section in the panel — then plain FTP upload is the only option left
  • Large binary files (video, database dumps) change regularly in the repository — Git handles these poorly without Git LFS, and LFS isn't available on every server

How to Verify the Deployment Succeeded

# On the server: compare the current commit with what was pushed locally
cd /var/repo/mysite.git && git log -1 --oneline

# In the working directory: file modification time should be fresh
stat -c '%y' /var/www/mysite/index.php

# The site responds without a 500 error
curl -I https://yourdomain.com/
  1. Compare the commit hash in the bare repository with what was pushed locally — they must match
  2. Check the file modification time in the working directory with stat — it should update at the moment of deploy
  3. Open the site with curl -I: if the hook ran but composer install failed, the files will be updated but the site will still be broken
  4. In GitHub Actions, check the Actions tab — the latest workflow run must be green, even if git push completed without errors

Setting up key-based access instead of a password for the server itself is covered in the article on SSH access on shared hosting, and automating follow-up tasks after deploy is covered in the article on cron jobs in cPanel.

Frequently asked questions

How do you deploy without downtime and roll back instantly, since git checkout -f isn't atomic?

A plain checkout -f swaps files in the working directory, so the site briefly sees an incomplete set of files. For reliability, each release deploys into its own folder with a symlink pointing at the current version: the hook builds the release, verifies it, then flips the symlink. Rolling back means pointing the symlink at the previous folder.

What happens to files edited manually right on the server when the next git push runs?

The hook runs git checkout -f, which forcibly overwrites every tracked file with the version from the commit — any manual edit to config or code made directly on the server disappears without warning at the next deploy. User uploads and media are untouched only if they aren't tracked by the repository and are listed in .gitignore.

How do you roll back to a previous version of the site after a deploy breaks something?

On a VPS with a bare repository, switch the working directory to the needed commit with git checkout and rerun the hook steps manually, including composer install and migrations if needed. In cPanel, it's simpler to point Git Version Control at the earlier commit and click update from remote again. For an instant rollback, a releases-and-symlink scheme helps most.

Is it worth switching to GitHub Actions if git push through a bare repository already works?

A bare repository with a hook is faster and simpler for a single developer — deployment happens right after push, no external service involved. GitHub Actions pays off once a build, tests, or a verified composer install step are needed before going live: the pipeline halts on a build failure, while a plain hook applies the code regardless.

Was this article helpful?
← Back to Knowledge Base Ask Support