GitLab push rules and server hooks Jump to heading

GitLab gives you two ways to enforce policy on pushes. Push rules are a settings page: commit message patterns, branch name patterns, file name and size restrictions, signed-commit requirements, checks that the author is a real GitLab user. They need no code and work on GitLab.com. Server hooks are scripts you install on a self-managed instance’s Git storage: full pre-receive, update and post-receive hooks with complete control. Teams on self-managed GitLab sometimes write server hooks for things a push rule already does, and teams on GitLab.com sometimes assume custom hooks are possible when they are not. This page maps which tool covers which policy, how to install server hooks in current GitLab versions, and how they combine, within server-side hook enforcement.

When to use this approach Jump to heading

  • Your repositories are on GitLab — GitLab.com or self-managed.
  • You need policies enforced at push time, not just as CI checks after the fact.
  • You are deciding between configuring push rules and writing server hooks.
  • You maintain hook logic for other servers too, as in writing an update hook for per-branch policy.

Step 1 — Know what push rules cover Jump to heading

Push rules are configured per project or as group and instance defaults. Each rule is evaluated by GitLab during the push, before refs update.

What GitLab push rules can enforcePush rules cover commit messages by regular expression, branch names by pattern, rejecting files by name or size, requiring the committer to be a verified GitLab user, preventing secrets by file name, and rejecting unsigned commits. Anything beyond these needs a server hook or CI.Messagesrequired / forbiddenregexBranch namesregexFilesname regexmax sizeIdentitycommitter is a usersigned commitsif a policy fits one of these boxes, use the push rule — no code to maintain
# Inspect and set a project's push rule through the API
glab api "projects/$PROJECT_ID/push_rule"
glab api -X PUT "projects/$PROJECT_ID/push_rule" \
  -f commit_message_regex='^(feat|fix|chore|docs|refactor|test|perf)(\(.+\))?: .+ [A-Z]+-[0-9]+' \
  -f branch_name_regex='^(main|release/.+|(feat|fix|chore)/[a-z0-9._-]+)$' \
  -F max_file_size=5 -F reject_unsigned_commits=true

Some push rules, such as rejecting unsigned commits, depend on the GitLab tier. Group-level push rules apply to new projects, so existing projects need updating explicitly.

Step 2 — Recognise what needs a server hook Jump to heading

Push rules are regular expressions and fixed checks. Anything that needs logic, state or external data does not fit: per-branch permission policies by group, checking that every new commit is signed by a key in a team allowed-signers file, verifying submodule URLs against an allow-list, rejecting pushes during a deploy freeze. On self-managed GitLab those can be server hooks; on GitLab.com they must be CI jobs combined with protected branches and merge request rules.

Push rule, server hook or CI?If the policy is a pattern on messages, branch names or files, use a push rule. If it needs logic or external data and the instance is self-managed, use a server hook. On GitLab.com, implement it as a required pipeline job with protected branches.What does the policy need?a regex or fixed checkPush ruleno codelogic, self-managedServer hookpre-receive / updatelogic, GitLab.comRequired CI job+ protected branchserver hooks are unavailable on GitLab.com — plan for that if you might migrate

Step 3 — Install a server hook on self-managed GitLab Jump to heading

Current GitLab versions store repositories in Gitaly, and server hooks are uploaded per repository with the gitaly command rather than copied into directories by hand. The hook files are packaged as a tarball containing pre-receive, update or post-receive, optionally with .d directories for several scripts.

# Package hooks: a pre-receive and a directory of update scripts
mkdir -p hooks/update.d
cp pre-receive hooks/ && cp branch-policy.sh hooks/update.d/
chmod +x hooks/pre-receive hooks/update.d/*
tar -C hooks -cf hooks.tar .

# Upload to one repository (run on a Gitaly node)
sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set \
  --storage default --repository "@hashed/ab/cd/abcd….git" --config /var/opt/gitlab/gitaly/config.toml < hooks.tar

Global server hooks for every repository can be configured in Gitaly’s configuration as a custom hooks directory. Prefer per-repository hooks for policies that apply to some projects only.

# Verification: list hooks installed for the repository
sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks get --storage default \
  --repository "@hashed/ab/cd/abcd….git" --config /var/opt/gitlab/gitaly/config.toml | tar -tf -

Step 4 — Use GitLab’s environment in hooks Jump to heading

GitLab passes variables to server hooks that identify the pusher and the project, so hooks can apply user-specific policy without parsing SSH keys.

#!/bin/sh
# pre-receive on GitLab — GL_USERNAME, GL_ID and GL_PROJECT_PATH are provided
echo "push by $GL_USERNAME to $GL_PROJECT_PATH" >&2
while read -r old new ref; do
  case "$ref" in
    refs/heads/release/*)
      echo "$GL_USERNAME" | grep -Eqx '(alice|priya|release-bot)' || {
        echo "GL-HOOK-ERR: only release managers may push to ${ref#refs/heads/}"; exit 1; } ;;
  esac
done

Messages prefixed with GL-HOOK-ERR: are shown in the GitLab interface as well as in the pusher’s terminal, which matters for actions taken through the web, such as merging.

Step 5 — Combine push rules, hooks and CI without overlap Jump to heading

Run each policy in exactly one place where possible, and document which. Duplicating a rule in a push rule and a hook means two error messages for one problem and two places to update.

A layered GitLab policy setupPush rules handle message patterns, branch names, file sizes and signed commits. A server hook adds per-branch permission logic and a team signer check. Protected branches require merge requests. A required pipeline job runs checks that need the merged code.each policy in one placePush rulespatterns, sizes, signed commitsServer hooks (self-managed)logic: per-branch, signer listProtected branchesMR required, no force-pushRequired pipelinemerged-result testswrite down which layer owns which rule — it saves the next admin an afternoon

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Can GitLab.com users install server hooks? Jump to heading

No. Server hooks require access to the Git storage nodes, which only self-managed administrators have. On GitLab.com, use push rules, protected branches and required pipelines.

Do server hooks run for merges done in the GitLab interface? Jump to heading

Yes. Merges, file edits and other web operations that update refs go through the same receive path and run pre-receive and update hooks, which is why GL-HOOK-ERR messages are shown in the interface.

How do we test GitLab server hooks? Jump to heading

Upload them to a scratch project first and push representative commits, then roll out to real projects. The general approach is in testing server-side hooks safely.