KhueApps
Home/DevOps/Fix 'fatal: refusing to update checked out branch' on push

Fix 'fatal: refusing to update checked out branch' on push

Last updated: October 07, 2025

Overview

You see this error when you push to a remote repository whose currently checked-out branch is the same branch you’re updating:

fatal: refusing to update checked out branch

Git blocks this because updating a checked-out branch in-place can clobber the remote working tree. In DevOps contexts (deploying to servers), configure the remote properly rather than forcing the push.

Quickstart (recommended)

Choose one of these approaches based on your needs:

  • Recommended: Push to a bare repo, then deploy from a separate working tree (clone or hook-driven checkout).
  • Simple deployments: Allow updating the checked-out branch with receive.denyCurrentBranch=updateInstead.
  • Legacy/deploy hooks: Keep non-bare repo and use post-receive to checkout/reset.

Minimal working example (reproduce and fix with updateInstead)

This local simulation shows the error and one safe fix.

# Simulate a server-side non-bare repo with a checked-out branch
mkdir -p /tmp/remote-app && cd /tmp/remote-app
git init -b main
printf "hello\n" > README.md
git add README.md
git commit -m "init"

# Simulate a client
cd /tmp
git clone /tmp/remote-app client
cd client
echo "change" >> README.md
git commit -am "update"

# This push will fail with: 'fatal: refusing to update checked out branch'
git push origin main

# Fix on the 'server': allow in-place updates if the working tree is clean
cd /tmp/remote-app
git config receive.denyCurrentBranch updateInstead

# Retry from the client
cd /tmp/client
git push origin main

# Server working tree now reflects the new commit

Notes:

  • updateInstead updates the remote working tree only if it’s clean and no files would be overwritten. If dirty, the push is rejected.
  • Requires Git ≥ 2.3.

Option A: Use a bare remote (best practice)

This avoids the error entirely and is the standard approach for shared remotes.

  1. Create a bare repo on the server:
# On server
sudo mkdir -p /srv/app.git && cd /srv/app.git
git init --bare
  1. Point your local repo to the bare remote and push:
# On client
cd /path/to/local/app
git remote set-url origin user@server:/srv/app.git
# or: git remote add origin user@server:/srv/app.git
git push -u origin main
  1. Deploy from the bare repo to a working directory (two common ways):
  • Separate clone pull-based:
# On server
sudo mkdir -p /srv/app && cd /srv/app
git clone /srv/app.git .
# To update later: git fetch && git reset --hard origin/main
  • Hook-driven checkout from the bare repo (push triggers deploy):
# On server, inside /srv/app.git
cat > hooks/post-receive <<'SH'
#!/usr/bin/env bash
set -euo pipefail
GIT_DIR="/srv/app.git"
WORK_TREE="/srv/app"
branch="main"
mkdir -p "$WORK_TREE"
# Checkout exactly what was pushed
git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" checkout -f "$branch"
SH
chmod +x hooks/post-receive

Pros:

  • Safe shared remote; no checked-out branch in the repo you push to.
  • Clear separation of repository storage and working tree.

Option B: Allow in-place updates with updateInstead

For simple server-side deployments where the remote repo doubles as the working tree.

  1. On the server repo (non-bare):
cd /path/to/non-bare/repo
# Update the working tree on push if clean
git config receive.denyCurrentBranch updateInstead
  1. Push from the client as usual:
git push origin main

Behavior:

  • If the server working tree has no conflicting changes, the push updates files.
  • If untracked/modified files would be overwritten, the push is refused.

Option C: post-receive hook with ignore

Legacy approach when you want to force your own checkout logic.

  1. On the server non-bare repo:
cd /path/to/non-bare/repo
# Allow receiving updates even when checked out
git config receive.denyCurrentBranch ignore
  1. Add a post-receive hook to refresh the working tree predictably:
cat > .git/hooks/post-receive <<'SH'
#!/usr/bin/env bash
set -euo pipefail
branch="main"
# Reset working tree to the pushed commit
read old new ref
if [ "$ref" = "refs/heads/$branch" ]; then
  git fetch origin "$branch"
  git reset --hard "$new"
fi
SH
chmod +x .git/hooks/post-receive

Warning: ignore disables important protections. Ensure your hook fully resets the tree. Prefer updateInstead or a bare repo when possible.

Step-by-step decision guide

  • Use a bare remote if multiple users/CI push, or you want a clean deploy pipeline.
  • Use updateInstead for a simple, single-writer server deployment with a clean working tree.
  • Use hooks with ignore only when legacy constraints apply and you understand the risks.

Pitfalls and gotchas

  • Dirty server working tree: updateInstead rejects pushes if files would be overwritten. Clean it (stash/commit/reset) or deploy to a different directory.
  • Permissions: hooks must be executable by the user receiving pushes; consider core.sharedRepository for shared UNIX groups.
  • Branch mismatch: confirm you’re pushing the branch actually checked out or tracked in deploy scripts.
  • Force pushes don’t help: --force does not bypass the safety check on the remote.
  • Concurrency: serialize deployments if multiple pushes can occur (use flock inside hooks).
  • Non-bare clones as remotes are fragile: prefer a bare remote and a separate working directory.

Performance notes

  • Bare + deploy clone: efficient; objects are fetched once, deploy clone updates via fast resets.
  • Hook-driven checkout: checkout cost scales with changed files; keep working tree minimal (consider sparse-checkout for large monorepos).
  • Network: push only required refs; avoid pushing tags unless needed (git push --follow-tags if necessary).
  • Maintenance: run git gc on the server periodically to keep packfiles compact.
  • CI/CD: prefer fetch+reset over pull in hooks to avoid merge work and reduce I/O.

FAQ

  • Why does this error happen? Because you push to a non-bare repo where the target branch is currently checked out; Git refuses to overwrite a live working tree.

  • Will --force fix it? No. The refusal happens server-side before ref updates; force doesn’t override receive.denyCurrentBranch.

  • How do I check if the remote is bare? On the server: git rev-parse --is-bare-repository (true means bare).

  • How do I see the current policy? On the server repo: git config --get receive.denyCurrentBranch.

  • What Git version is needed for updateInstead? Git 2.3 or later.

  • Can I avoid touching the server at all? Yes: push to a bare repo and use a separate deployment agent (CI/CD) that pulls/fetches and resets in a working directory.

Series: Git

DevOps