Git hooks are scripts Git runs automatically at certain points: before a commit is created, before a push, or after a server receives new commits. Teams use them to catch problems early (lint errors, secrets, bad commit messages) and, on servers, to trigger simple deployments.
Where hooks live
Each repository has .git/hooks/, with sample files (pre-commit.sample and others). A hook is just an executable file named after the event:
ls .git/hooks
cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Hooks in .git/hooks aren’t committed, so they don’t travel with the repository. Sharing options are below.
Common client-side hooks
| Hook | Runs | Typical use |
|---|---|---|
pre-commit |
Before a commit is created | Lint, format, block secrets or large files |
prepare-commit-msg |
Before the message editor opens | Insert a ticket number from the branch name |
commit-msg |
After the message is written | Enforce message conventions |
pre-push |
Before pushing | Run tests, block pushes to main |
post-checkout, post-merge |
After switching branches or pulling | Reinstall dependencies, regenerate files |
A non-zero exit code aborts the action (for pre-* and commit-msg hooks).
Example: block accidental secrets and large files
#!/bin/sh
# .git/hooks/pre-commit
if git diff --cached -U0 | grep -E -i '(api[_-]?key|secret|password)\s*[:=]\s*["'"'"']?[A-Za-z0-9/+]{12,}' ; then
echo "Possible secret in staged changes. Commit aborted."; exit 1
fi
git diff --cached --name-only -z | xargs -0 -r du -k 2>/dev/null | awk '$1 > 5120 {print "File too large: " $2; bad=1} END {exit bad}'
Dedicated tools (gitleaks, detect-secrets) are far better at finding secrets than a grep. See secrets management.
Example: enforce Conventional Commits
#!/bin/sh
# .git/hooks/commit-msg
if ! head -1 "$1" | grep -Eq '^(feat|fix|docs|chore|refactor|test|ci|perf)(\(.+\))?: .+'; then
echo "Commit message must look like: feat(api): add export endpoint"; exit 1
fi
Share hooks with the team
Option 1: a committed hooks directory
mkdir .githooks && mv .git/hooks/pre-commit .githooks/
git config core.hooksPath .githooks # each developer runs this once
Option 2: the pre-commit framework (language-agnostic, many ready-made hooks):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks
pip install pre-commit
pre-commit install
pre-commit run --all-files
JavaScript projects often use Husky + lint-staged instead.
Client hooks can be skipped (git commit --no-verify), so run the same checks in CI as the real gate. See CI/CD pipeline explained.
Server-side hooks
On a server hosting a bare repository, hooks can enforce rules or deploy:
| Hook | Use |
|---|---|
pre-receive |
Reject pushes (e.g. force pushes, policy violations) |
update |
Per-branch checks |
post-receive |
Notify, or deploy after a push |
A minimal push-to-deploy setup for a static site:
# on the VPS
git init --bare /srv/git/site.git
cat > /srv/git/site.git/hooks/post-receive <<'EOF'
#!/bin/sh
GIT_WORK_TREE=/var/www/example.com git checkout -f main
EOF
chmod +x /srv/git/site.git/hooks/post-receive
# on your computer
git remote add live ssh://deploy@your-vps/srv/git/site.git
git push live main
For anything beyond simple sites, prefer CI/CD pipelines, which handle builds, tests and rollbacks. See WordPress Git deployment and GitHub Actions deploy to VPS. Hosted platforms (GitHub, GitLab, Gitea) offer webhooks and protected branches instead of custom server hooks.
Quick reference
| Task | How |
|---|---|
| Enable a hook | Executable file in .git/hooks/<name> |
| Share hooks | git config core.hooksPath .githooks |
| Framework | pre-commit install |
| Skip once | git commit --no-verify |
| Push-to-deploy | post-receive in a bare repo |
Related: Git branching strategies and Git commands cheat sheet.