Skip to content
Extra 20% off HostingerWEBHOST2026Get my link →
DiscountOffer.Store
Tutorials

Auto-deploy to a Hostinger VPS with GitHub Actions (push to deploy)

8 min read

Every push to main deploys your app to the VPS. A dedicated SSH key, pinned host keys, a locked-down forced command and a workflow under 30 lines, with no third-party actions.

How it fits together

The simplest reliable setup has GitHub do nothing clever. On every push to `main`, a workflow opens an SSH connection to your VPS and runs the deploy script that already lives there. The server pulls the code, builds and restarts. If the script works when you run it by hand, it works from CI.

This guide assumes you have a working `deploy.sh`, like the one at the end of our Next.js guide. The same pattern works for Laravel, Django or anything else; only the script changes.

Step 1 — Make a key just for CI

Never reuse your personal SSH key. Make a separate one with no passphrase, so you can revoke CI access without touching your own login.

On your laptop:
bash
ssh-keygen -t ed25519 -N "" -C "github-actions" -f ./gha_deploy
# gha_deploy      -> private key, goes into a GitHub secret
# gha_deploy.pub  -> public key, goes onto the server

Step 2 — Allow the key to run one command only

Add the public key to the deploy user's `authorized_keys` with a forced command. Whatever the client asks for, the server runs only `deploy.sh`. If the private key ever leaks, the worst anyone can do is trigger a deploy of your own code.

On the VPS, as deploy, add one line (all on a single line):
bash
nano ~/.ssh/authorized_keys

command="/var/www/myapp/deploy.sh",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA...your-public-key... github-actions

Step 3 — Store the secrets in GitHub

In the repository, open Settings → Secrets and variables → Actions and add three secrets. Pinning the host key matters: without it, the workflow would trust whatever machine answers on that IP.

Get the value for SSH_KNOWN_HOSTS:
bash
ssh-keyscan -t ed25519 YOUR_SERVER_IP
SecretValue
SSH_HOSTYour VPS IP or domain
SSH_KEYThe full contents of gha_deploy (private key)
SSH_KNOWN_HOSTSThe line printed by ssh-keyscan

Step 4 — The workflow

Commit this file. `concurrency` queues deploys so two quick pushes can't build on top of each other.

.github/workflows/deploy.yml
yaml
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

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

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts

      - name: Deploy
        run: ssh deploy@${{ secrets.SSH_HOST }}

Step 5 — Make failures visible

Because `deploy.sh` starts with `set -euo pipefail`, a failed build exits non-zero, the SSH command fails, and the workflow goes red. GitHub emails you, and the live app keeps running the previous build because PM2 was never restarted.

  • Run tests in an earlier job and add `needs: test` to the deploy job, so broken code never reaches the server.
  • Keep the build on the server only while builds are quick. If they start eating the VPS's CPU while it serves traffic, build in Actions and rsync the output instead.
  • Add a protected environment in GitHub if you want a manual approval before production deploys.

Troubleshooting

The errors you're most likely to see, and what they mean:

ErrorCause and fix
Host key verification failedSSH_KNOWN_HOSTS is empty or for a different key type. Re-run ssh-keyscan and paste the whole line.
Permission denied (publickey)The public key isn't in the deploy user's authorized_keys, or the file is group-writable. Run chmod 600 ~/.ssh/authorized_keys.
pm2: command not foundNon-interactive SSH doesn't load your shell profile. Use the full path from `which pm2` inside deploy.sh.
Build killed / exit 137Out of memory during the build. Add swap, or build in Actions instead.
Extra 20% off with WEBHOST2026Claim 20% off →

Frequently asked questions

Can I use GitHub Actions with Hostinger shared hosting?

Yes, but over FTP or SFTP rather than SSH commands: build in Actions, then upload the output folder. Shared hosting can't run a build or a long-lived process for you.

Is it safe to store an SSH key in GitHub secrets?

Secrets are encrypted and masked in logs. Pair that with a CI-only key restricted by a forced command, and a leaked key can only trigger a deploy of your own code.

Does this cost anything?

GitHub Actions includes free minutes for private repos and is free for public ones. This workflow uses well under a minute per deploy, since the build runs on your VPS.

Ready to save an extra 20% with coupon WEBHOST2026?

Get my discount link →

Disclosure: links to Hostinger on this page are referral links. If you buy through them we may earn a commission, at no extra cost to you. It doesn't change what we recommend. About us

Ready to claim your Hostinger discount?

Generate your personalised cart link with coupon WEBHOST2026 attached — it takes 20 seconds.