Branch Protection & Repository Policies: A Complete GitHub Guide

Branch Protection & Repository Policies
Branch protection rules are one of the most important safeguards in any professional GitHub workflow. They prevent direct pushes to critical branches, enforce code review requirements, and ensure your CI pipeline stays green before any code reaches production.
In this guide you will learn how to configure branch protection from scratch, set up required status checks, use CODEOWNERS files, and leverage GitHub's newer Rulesets feature for organisation-wide enforcement.
Why Branch Protection Matters
Without protection, any contributor with write access can push directly to main. This means:
- A typo in a migration file goes straight to production
- A half-finished feature breaks the build for the entire team
- There is no audit trail of who approved what and when
Branch protection turns main into a guarded gate: code can only enter through a reviewed, tested pull request. This single change transforms a chaotic repo into a reliable delivery pipeline.
Setting Up Branch Protection Rules
Step 1: Navigate to Branch Settings
- Open your repository on GitHub
- Click Settings in the top navigation bar
- In the left sidebar click Branches
- Under "Branch protection rules" click Add rule
Step 2: Target Your Branch
In the "Branch name pattern" field enter the branch you want to protect. Common patterns:
| Pattern | Matches |
|---|---|
main | Exactly the main branch |
release/* | All branches starting with release/ |
v[0-9]* | Branches like v1, v2.0, v3.1-rc |
** | Every branch in the repository |
Step 3: Core Protection Options
Require a pull request before merging
This is the most fundamental rule. Enable it and GitHub will block any direct push to the protected branch. All changes must arrive via a pull request.
Sub-options include:
- Required approvals: Set to
1for small teams,2for larger ones - Dismiss stale pull request approvals when new commits are pushed: Forces re-review after every new commit, preventing "approve then sneak in changes" attacks
- Require review from Code Owners: Only the designated owners of changed files can approve (see CODEOWNERS section below)
- Restrict who can dismiss pull request reviews: Prevents developers from dismissing their own reviews
Require status checks to pass before merging
This links your CI pipeline to the merge button. Until every required check reports success, the merge button stays greyed out.
To add a required check:
- Enable "Require status checks to pass before merging"
- In the search box type the name of your GitHub Actions job
- The job must have run at least once on the repository before it appears in search results
Example: if your workflow file contains this job:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm testSearch for test and it will appear as a required check named test.
Require conversation resolution before merging
When a reviewer leaves a comment, it becomes a blocking conversation. The PR author must mark every conversation as "Resolved" before merging. This prevents developers from ignoring feedback.
Require signed commits
All commits must be signed with a verified GPG or SSH key. This proves that commits actually come from the claimed author and prevents commit spoofing.
Require linear history
Disables merge commits entirely — contributors must rebase or squash before merging. This keeps your git log clean and bisect-friendly.
Do not allow bypassing the above settings
By default, repository administrators can bypass all protection rules. Enabling this option applies the rules even to admins. For any production repository this should be on.
Required Status Checks in Practice
Here is a complete GitHub Actions workflow that integrates cleanly with branch protection:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main, release/*]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run buildWith this workflow, add lint, test, and build as required status checks. A PR cannot merge until all three pass on the latest commit.
Important: If you rename a job in your workflow, GitHub treats it as a brand-new check. You must update the required checks in branch settings or the old check name will remain required but never run (permanently blocking merges). Always update both at the same time.
The CODEOWNERS File
CODEOWNERS lets you assign automatic review requirements based on which files are changed. When a PR touches a file matched by a CODEOWNERS rule, the designated owners are automatically added as required reviewers.
Create the file at one of these locations (GitHub checks in order):
.github/CODEOWNERSCODEOWNERS(root)docs/CODEOWNERS
CODEOWNERS Syntax
# Each line: [pattern] [owner1] [owner2] ...
# Default owner for everything
* @org/core-team
# Frontend files require frontend team approval
/src/components/ @org/frontend-team
/src/styles/ @org/frontend-team
*.css @org/frontend-team
# Database migrations need DBA sign-off
/prisma/migrations/ @alice @bob
# CI/CD changes need DevOps review
/.github/ @org/devops-team
/Dockerfile @org/devops-team
docker-compose.yml @org/devops-team
# Security-sensitive files
/src/auth/ @carol @security-team
/src/api/payments/ @carol @security-teamRules are matched from bottom to top — the last matching pattern wins. If a PR changes both src/components/Button.tsx (matched by /src/components/) and src/auth/session.ts (matched by /src/auth/), both teams are added as required reviewers.
To make CODEOWNERS effective, enable "Require review from Code Owners" in your branch protection rule.
GitHub Rulesets (The Modern Approach)
Rulesets are GitHub's newer, more powerful alternative to branch protection rules. They offer several advantages:
- Organisation-wide enforcement: Apply rules across all repositories in your org
- Bypass lists: Grant specific users or teams bypass permissions with a proper audit trail
- Push rules: Block pushes that match certain file paths or commit message patterns
- Import/export: Share ruleset configurations between repositories as JSON
Creating an Organisation Ruleset
- Go to your GitHub organisation settings
- Click Rules → Rulesets in the sidebar
- Click New ruleset → New branch ruleset
- Set Enforcement status to
Active - Under Target branches add the patterns to protect
- Configure rules (similar options to branch protection, plus additional push restrictions)
- Add bypass actors if needed (emergency access for on-call engineers)
Push Rules (Rulesets Only)
Rulesets support push rules that block problematic content before it enters the repository:
- Restrict file paths: Block pushes that modify
.envfiles or secret configs - Restrict file path length: Prevent excessively deep directory nesting
- Restrict file extensions: Block binary files like
.exeor large archives - Restrict commit metadata: Enforce commit message format (e.g., must start with
feat:,fix:,chore:) - Max file size: Block commits containing files over a defined size limit
Common Configuration Mistakes
Forgetting to check "Include administrators"
If admins can bypass your rules, your rules provide a false sense of security. Always enable "Do not allow bypassing the above settings" for production branches.
Required checks that never run
A required check only appears in the merge checklist when it runs against the PR's head commit. If your workflow only triggers on push but not pull_request, the check never runs and the PR is permanently blocked. Always trigger checks on pull_request.
Stale approval loophole
Without "Dismiss stale pull request approvals when new commits are pushed", a developer can get approval, push a breaking change, and merge immediately. Always enable stale approval dismissal on critical branches.
Too many required reviewers
Requiring 4 approvals on every PR creates bottlenecks. Most teams do well with 1-2 required reviewers. Reserve higher counts for especially sensitive repositories.
Branch Protection for Monorepos
Large monorepos with multiple services need more nuanced rules. Consider:
- Path-based CODEOWNERS: Route reviews to the team that owns each service directory
- Conditional required checks: Use job conditionals in GitHub Actions to only run relevant tests based on changed paths
- Rulesets per service: Create separate rulesets with different bypass lists for different service directories
Example path-conditional workflow:
jobs:
test-api:
if: |
contains(github.event.pull_request.changed_files, 'services/api/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd services/api && npm testFrequently Asked Questions
Q: What are branch protection rules in GitHub and what can they enforce?
Branch protection rules prevent direct pushes, force-pushes, and deletions on important branches (typically main or release/*). They can require: pull request reviews before merging, passing status checks (CI tests), signed commits, linear history (no merge commits), and conversation resolution. Set them in repository Settings → Branches → Add rule. GitHub Enterprise adds rulesets with more granular controls and organisation-wide enforcement.
Q: How do you require a specific CI check to pass before a PR can be merged?
In the branch protection rule, enable "Require status checks to pass before merging" and search for the check name (the job name from your workflow, e.g. test). The check must have run at least once before it appears in the search. Once added as a required check, GitHub blocks the merge button until that specific check reports success on the latest commit of the PR.
Q: Can repository admins bypass branch protection rules?
By default yes — repository admins can bypass most protections. To prevent this, enable "Do not allow bypassing the above settings" in the branch protection rule. For organisation-critical branches, use GitHub's Rulesets feature which supports bypass lists with explicit named users or teams, giving you fine-grained control over who can override protections in emergency situations.
Q: What is the difference between branch protection rules and rulesets?
Branch protection rules are per-repository and have been in GitHub since the early days. Rulesets are newer, support organisation-wide enforcement, offer push rules, and include a bypass actor system with an audit log. For new setups, prefer rulesets. For existing repos already using branch protection, migration is straightforward — the core options are identical.
Q: How do I set up CODEOWNERS without blocking all merges?
First create the CODEOWNERS file and merge it into main before enabling "Require review from Code Owners" in branch settings. If you enable it before the file exists, GitHub treats the entire repository as unowned and adds the repo owner as a required reviewer on every PR. Always commit the CODEOWNERS file first.
Key Takeaway
Branch protection rules transform your repository from an open playground into a production-grade delivery pipeline. The combination of required reviews, mandatory CI checks, and CODEOWNERS file creates multiple layers of defense against bad code reaching production. Start with the basics — require PRs and at least one status check — and add more rules as your team grows. For organisations managing multiple repositories, migrate to Rulesets for centralised, auditable enforcement.
Read next: GitHub Actions: Building CI/CD Pipelines →
Part of the GitHub Mastery Course — from beginner to professional.
