Back to Blog
Lesson 21 of the CI/CD: Continuous Integration from Scratch course
DevOpsJuly 27, 20264 min read

Branch Protection Rules: Securing Your Main Branch on GitHub

Stop accidental pushes to your main branch. Learn to enable branch protection, enforce status checks, and secure your GitHub repository with CI gates.

githubdevopssecurityci-cdgit
Cluster of padlocks tied to a tree branch in a natural, woodland background.

Previously in this course, we explored failing on lint errors to ensure our code meets quality standards. While automated linting catches errors, it doesn't stop a developer—or a stray script—from accidentally pushing broken code directly to your main branch.

In this lesson, we close that gap by implementing branch protection. This is the final gate in our CI/CD process, ensuring that no code reaches our primary branch without first passing all defined status checks.

Why Branch Protection Matters

In a professional environment, the main branch is the source of truth. If it breaks, your production deployment breaks. Relying on "team discipline" to avoid pushing to main is not a strategy; it’s a failure waiting to happen.

By enabling branch protection, we move from a "trusted" model to a "verified" model. GitHub becomes the enforcer, blocking any git push that bypasses your CI/CD pipeline.

Understanding the Protection Layers

When you enable branch protection, you aren't just flipping a switch; you are defining a security contract. The two most critical controls for our current project are:

  1. Restrict pushes: Prevents any user from pushing directly to the branch. This forces everyone to use Pull Requests.
  2. Require status checks: Forces the repository to wait for CI (like your linting and testing jobs) to report a "success" status before the "Merge" button becomes active.

Hands-on: Applying Protection Rules

You don't need a configuration file for this; it’s a repository setting. Follow these steps to secure your project:

  1. Navigate to your repository on GitHub.
  2. Click the Settings tab.
  3. On the left sidebar, click Branches.
  4. Click Add branch protection rule.
  5. In the "Branch name pattern" field, type main.
  6. Check Require a pull request before merging.
  7. Check Require status checks to pass before merging.
  8. In the status checks search box, select the specific jobs you want to require (e.g., your lint or test jobs).
  9. Click Create or Save changes.

Worked Example: The "Failed Check" Scenario

Once these rules are active, the behavior of your repository changes immediately. Imagine a developer tries to merge a PR that contains failing tests:

  • Before Protection: The "Merge" button is always green. A mistake could easily be merged.
  • After Protection: The GitHub UI will show a message: "Required status check 'test' failed." The "Merge" button will be greyed out and unclickable.

Even if someone tries to force the issue via the command line, the server-side hook will reject the push:

Bash
$ git push origin main
! [remote rejected] main -> main (protected branch hook declined)
error: failed to push some refs to 'github.com/your-repo'

Practice Exercise

Now it’s your turn to lock down the repository from our previous lessons:

  1. Verify that your CI pipeline is currently passing.
  2. Follow the steps above to enable branch protection for main.
  3. Attempt to push a small, non-breaking change directly to main from your local machine.
  4. Observe the rejection error in your terminal—congratulations, your security gate is working!

Common Pitfalls

  • Forgetting to set the pattern: Ensure the branch name pattern exactly matches your branch name (e.g., main vs master).
  • Missing status checks: If you rename a job in your YAML file, you must update the branch protection settings to reflect the new job name, or the "Merge" button will be permanently locked.
  • Admin lockouts: Admins can sometimes bypass these rules. Make sure you check "Do not allow bypassing the above settings" if you want to enforce these rules even for repository owners.

FAQ

Q: Can I still merge if the build is just "skipped"? A: No, branch protection requires a "success" state. If a job is skipped due to conditional logic, it might prevent a merge. Always ensure your status checks are configured to run under the conditions you expect.

Q: Does this work for other branches? A: Yes. You can use wildcards in the branch name pattern (like release/*) to protect multiple branches at once.

Recap

We’ve successfully moved from manual discipline to automated enforcement. By enabling branch protection, we've guaranteed that our main branch remains stable, verified, and secure, building on the foundations we laid when managing secrets and handling environment variables.

Up next: Now that the branch is protected, we need to ensure that humans are actually looking at the code, too. We’ll learn about Mandatory Reviewers.

Similar Posts