Blog
May 9, 2026

How I Used to Deploy: GitHub Actions + SSH + rsync

Before I moved everything to Cloudflare Pages, this was my deploy pipeline. It worked well — here's how it was set up.

Before I moved all my projects to Cloudflare Pages, I was running a self-hosted VPS and deploying everything via rsync over SSH through GitHub Actions. It was a solid setup — boring in the best way — and I ended up using the same pattern across most of my personal projects.

I’m documenting it here partly for posterity, partly because I still think it’s a good approach if you’re running your own server.

The setup

You need four things:

  1. An SSH key pair — generate one specifically for deploys, don’t reuse your personal key
  2. The public key added to ~/.ssh/authorized_keys on your server
  3. The private key added as a GitHub secret (SSH_KEY)
  4. Host, user, and deploy path as additional secrets

The workflow file

This is the pattern I settled on after a few iterations. Nothing fancy — build, then rsync the dist/ folder to the server.

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci && npm run build
      - name: Deploy
        uses: burnett01/rsync-deployments@7.0
        with:
          switches: -avzr --delete
          path: dist/
          remote_path: ${{ secrets.DEPLOY_PATH }}
          remote_host: ${{ secrets.SSH_HOST }}
          remote_user: ${{ secrets.SSH_USER }}
          remote_key: ${{ secrets.SSH_KEY }}

Rollback

No automatic rollback — if the build fails, the old files stay on the server because rsync only runs after a successful build step. If you push bad code that deploys successfully, revert the commit and push again.

For static sites, this is almost always fine.

Why I moved to Cloudflare Pages

Honestly, the VPS setup worked great. The reason I switched wasn’t that it broke — it was that Cloudflare Pages removed a whole layer of maintenance. No server to patch, no SSH keys to rotate, no worrying about disk space. Push to main and it just deploys.

If you’re already paying for a VPS and comfortable managing it, the rsync approach is totally viable. But if you’re starting fresh, Cloudflare Pages is hard to argue with for static sites.

[ Get in touch ]

Have a project?

Drop me a message about whatever you're building — I'm always happy to talk through ideas or potential collaborations.

Get in touch ↗ hello@jnutter.dev