Back to Blog
DevOps 4 min readJun 3, 2026

GitHub Branch Protection on Private Repos — Without Paying for Team Plan

GitHub Rulesets on private repos require the Team plan ($4/user/month). If you have a live production app and want to stop direct pushes tomainwithout paying, here is a GitHub Actions workaround that gets you 80% of the way there for free.

The Problem

You try to set up branch protection on a private repo and GitHub shows you this:

text
Your rulesets won't be enforced on this private repository
until you upgrade this organization account to GitHub Team.

The old branch protection rules UI also prompts an upgrade for private repos. For a small team or solo developer, $4/user/month is an unnecessary cost when you just want one simple rule: no direct pushes to main.

The Workaround

GitHub Actions runs on every push — including direct pushes tomain. Using the GitHub API, the workflow can check whether the incoming commit came from a merged pull request or was pushed directly. If it was a direct push, the workflow fails loudly.

This does not block the push (nothing free can do that on private repos), but it immediately fails CI, making the violation visible to the whole team and blocking any downstream deploy workflows from running.

The Workflow

Create .github/workflows/protect-main.yml in your repo:

yaml
name: Protect Main Branch

on:
  push:
    branches: [main]

jobs:
  check-pr-merge:
    runs-on: ubuntu-latest
    steps:
      - name: Check if push came from a merged PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          COMMIT_SHA="${{ github.sha }}"
          PUSHER="${{ github.actor }}"

          # Check if this commit is associated with a merged PR
          PR=$(gh api repos/${{ github.repository }}/commits/$COMMIT_SHA/pulls \
            --jq '.[0].merged_at' 2>/dev/null || echo "null")

          if [ "$PR" = "null" ] || [ -z "$PR" ]; then
            echo "❌ Direct push to main detected by $PUSHER"
            echo "All changes must go through a pull request."
            exit 1
          fi

          echo "✅ Commit came from a merged PR. Allowed."
💡

secrets.GITHUB_TOKEN is automatically available in every Actions run — no setup needed.

Block the Deploy Too

The real enforcement comes from making your deploy workflow depend on this check. Add needs: check-pr-merge to your deploy job:

yaml
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  check-pr-merge:
    uses: ./.github/workflows/protect-main.yml  # reuse the check

  deploy:
    runs-on: ubuntu-latest
    needs: check-pr-merge   # deploy only runs if check passes
    steps:
      - name: Deploy
        run: echo "deploying..."

Now a direct push to main fails the check, and because deploy needs the check, the deploy never runs. Production is safe.

What This Gives You

text
✅  Direct push to main → CI fails immediately
✅  Deploy workflow blocked (needs check-pr-merge)
✅  Violation is visible in GitHub Actions tab
✅  Works on free plan, private repos
✅  No setup beyond adding the YAML file
❌  Cannot physically block the push from landing (needs Team plan)
❌  The commit does appear in git history for a few seconds

The Correct Workflow Going Forward

text
feature branch
    → push to dev
         → PR from dev to main
              → review & approve
                   → merge
                        → protect-main check passes (came from PR)
                             → deploy runs
                                  → production updated

Any push that bypasses this flow fails CI immediately and never reaches production.

Is It Worth Paying for GitHub Team?

If your team grows beyond 2-3 people, yes — GitHub Team adds:

  • True push blocking (the commit never lands)
  • Required status checks before merge
  • CODEOWNERS — specific reviewers required per file
  • Required number of approvals

For a solo developer or a small startup with a live production app, this workaround is a solid interim solution that costs nothing.

All postsGitHub · DevOps · CI/CD · Branch Protection · GitHub Actions

naresh@gowda:~$ built with Next.js + Tailwind + Framer Motion