Configure GitHub branch protection
Your default branch (main) is the project’s source of truth. Without protection, anyone with write access can rewrite its history or delete the branch entirely. This post explains why that matters and how to lock it down on GitHub by blocking force pushes and deletions, requiring pull requests with passing status checks, and automating branch cleanup.
Why protect the default branch
A force push rewrites the commit history of main. Other contributors’ local clones diverge from the remote, which causes conflicts and can silently lose work. CI audit trails tied to the overwritten commits vanish.
Deleting the branch removes the reference entirely: open pull requests close, and the repository has no defined default branch until someone restores it.
Requiring pull requests routes every change through a reviewable branch. Required status checks block the merge until tests and builds pass, so a broken change never reaches main.
Blocking force pushes and deletions, plus requiring pull requests with passing checks, requires no complex setup — normal workflows are unaffected.
Prerequisites
You need Admin or Owner permissions on the repository.
Protect the default branch against force pushes and deletion
- Go to Settings → Rules → Rulesets.
- Click New ruleset → New branch ruleset.
- Enter the name Protect against force pushes and deletions.
- Set Enforcement status to Active.
- Under Target branches, add the default branch (Include default branch).
- Check Restrict deletions.
- Check Block force pushes.
- Click Create.
Force pushes and deletions are now rejected. Normal pushes and PR merges continue to work.
Require pull requests and passing status checks
Before you start:
- A status check only appears in the rule picker if it has completed successfully in your repository within the past seven days. Run a workflow first (for example, open a pull request or push).
- For GitHub Actions workflows, the check name is the job name (the
name:field of the job, or its id if no name is set). Job names must be unique across workflows. - The workflow must trigger on
pull_requestevents. A workflow that runs only onpushnever reports on PRs and stays Pending and blocks merging.
Put these rules in a second ruleset. A bypass list applies to every rule in its ruleset, so a separate ruleset lets you bypass pull request rules without weakening force-push and deletion protection. Both rulesets apply to the default branch together.
- Go to Settings → Rules → Rulesets.
- Click New ruleset → New branch ruleset.
- Enter the name Require pull requests and passing status checks.
- Set Enforcement status to Active.
- Under Target branches, add the default branch (Include default branch).
- Check Require a pull request before merging.
- Set Required approvals. Use 0 if you maintain the repository alone: pull request authors cannot approve their own pull requests, so any higher number blocks every merge.
- Check Require status checks to pass.
- Check Require branches to be up to date before merging.
- Click + Add checks, then search for and select your check by job name.
- Set the check’s source to GitHub Actions, or leave it as Any source.
- Click Create.
Direct pushes to the default branch are now rejected. Pull requests merge only when the selected checks pass on a branch that is up to date with the base branch.
If you need to merge without an approval or a passing check, add a bypass to this ruleset only: under Bypass list, click + Add bypass, choose Repository admin, and set it to For pull requests only. Direct pushes stay blocked, and the first ruleset still applies to everyone.
Clean up and update pull request branches
- Go to Settings → General, then scroll to Pull Requests.
- Check Automatically delete head branches.
- Check Always suggest updating pull request branches.
Merged head branches are now deleted automatically after merge. Restore one with the Restore branch button on its pull request. The Update branch button now appears on every pull request whose branch is behind the base branch.



