In GitLab, set "Allowed to push and merge" to No one on your production branch and limit "Allowed to merge" to Maintainers. Then changes only land through merge requests. Be careful with wildcards, because when several rules match a branch the most permissive one controls.
The defaults
GitLab protects the default branch by default. But the documentation notes that an unconfigured "Allowed to push and merge" does not restrict push access, so to block direct pushes you must explicitly set it to No one. By default, Maintainers and Owners can unprotect a branch.
The two main settings
- Allowed to merge: roles that can merge through merge requests and create protected branches.
- Allowed to push and merge: roles, plus deploy keys, that can push and merge. Groups and individual users can be added on Premium and Ultimate.
GitLab's recommended setup: production branches use Maintainers only for merge and No one for push and merge, with code owner approval. Active development branches can allow Developers and Maintainers to merge.
For merge controls to work, set Allowed to push and merge to a broader set of users than Allowed to merge, as the documentation notes.
How to set it up
In a project, with Maintainer or Owner role: go to Settings, Repository, Branch rules, then Add branch rule. Enter a branch name or pattern, create the rule, and edit the two settings.
Group level protected branches exist for top level groups on Premium and Ultimate. That is a good way to apply one standard everywhere.
Force push and code owners
Force pushes are controlled by the Allowed to force push option. Leave it off for protected branches.
Code owner approval is Premium and Ultimate. With it on, merge requests need approval from a code owner for each matched rule, and direct pushes to the matched branch are denied. If multiple rules match, it is required when any of them enables it.
The wildcard trap
You can use * to protect many branches, such as production/* or *-stable. Names are case sensitive. When several rules match a branch, the most permissive rule controls its behavior.
Example: you protect release/* strictly, but an older rule for * lets Developers push. A release branch then accepts developer pushes. Audit overlapping patterns after every change.
A checklist
- Production and release branches: push No one, merge Maintainers.
- Force push off.
- Code owner approval where your tier allows.
- Check the list of who can unprotect. On Premium and Ultimate you can control this through the API.
- Review wildcard overlaps.
- Pair with merge request approval rules.
Safeguard policies and gates can add a security check to the merge request flow. The free scan is at gold.safeguard.sh/scan.
Quick check
Is the default branch safe out of the box?
It is protected, but direct push is not blocked until you set push and merge to No one.
Which rule wins when two match?
The most permissive one controls behavior, so review overlapping wildcards.
Sources
Last reviewed: 19 October 2026.