Git Push Rejected (Non-Fast-Forward): Safe Fix

"Git push rejected (non-fast-forward)" means the remote has commits you don't. Run git pull --rebase, fix conflicts, then push. Never force shared branches.
Every developer hits this one, usually at the end of a long day when all you want is to push and go home. Git isn't broken and your work isn't lost. Git is refusing to overwrite commits that someone else (or you, from another machine, or a bot) already pushed. On my own projects, pushing to main triggers a production deploy through GitHub Actions, so I care a lot about pushing the right history. This guide covers the safe fix, when force-pushing is acceptable, and the other "rejected" messages that look similar but mean something else.
Key takeaways
- The fix:
git pull --rebase origin main, resolve conflicts if any, thengit push. - "fetch first" and "non-fast-forward" are the same problem: the remote branch moved ahead of yours.
- Never use plain
--forceon a shared branch. If you must force, use--force-with-lease, which refuses if someone pushed in the meantime. - "protected branch" or "pre-receive hook declined" is a permissions rule on the server, not a history problem. Open a pull request.
- Set
pull.rebaseonce and you'll stop seeing the "divergent branches" hint.
The error
To github.com:acme/shop.git ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'github.com:acme/shop.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. If you want to integrate the remote changes, hint: use 'git pull' before pushing again.
A variant says (fetch first) and "the remote contains work that you do not have locally". Same cause, same fix.
Why it happens
A push is only accepted automatically if it's a fast-forward: the remote branch's latest commit must be an ancestor of yours, so the remote can simply move its pointer forward. If someone pushed commit C while you were making commit D on top of the older commit B, the histories have split. Accepting your push would delete C from the branch. Git refuses, and asks you to combine the two first.
Common ways to end up here:
- A teammate pushed to the same branch.
- You committed from another laptop, or edited a file in the GitHub web editor.
- You created the repo on GitHub with a README or licence, then tried to push a local project.
- A bot committed: Dependabot, a release workflow, a formatter.
- You amended or rebased commits you had already pushed.
The fix: pull with rebase, then push
git status # commit or stash any uncommitted work first git pull --rebase origin main git push origin main
--rebase takes your local commits, sets them aside, updates your branch to match the remote, then replays your commits on top. History stays a straight line, without an extra "Merge branch 'main' of github.com..." commit. A plain git pull (merge) is also correct; it just adds a merge commit.
If the rebase stops with a conflict
git status # lists files with conflicts # edit each file, keep the right lines, remove the <<<<<<< ======= >>>>>>> markers git add path/to/file git rebase --continue # changed your mind? git rebase --abort # back to exactly where you started
During a rebase, "ours" and "theirs" are swapped compared with a merge: "ours" is the remote's version you're rebasing onto. Read the conflict content, not the labels. For lock files like package-lock.json, take the remote version and regenerate it with a fresh install rather than merging by hand.
"You have divergent branches" when you pull
hint: You have divergent branches and need to specify how to reconcile them. fatal: Need to specify how to reconcile divergent branches.
Newer versions of Git make you choose merge or rebase instead of guessing. Pick once:
git config --global pull.rebase true # my preference git config --global rebase.autoStash true # stash and re-apply local edits automatically
"refusing to merge unrelated histories"
You created the GitHub repo with a README, then tried to push a project you started locally. The two histories have no common commit. If the remote only has that README:
git pull origin main --allow-unrelated-histories git push -u origin main
When is force-pushing OK?
Only when you deliberately rewrote history on a branch that only you use, for example after squashing commits on your feature branch before a review. Even then, use the safe version:
git push --force-with-lease origin feature/checkout
--force-with-lease checks that the remote branch is still where you last saw it. If a teammate pushed in the meantime, it refuses instead of erasing their work. Plain --force erases it without asking. On main, don't force at all: if main deploys to production, a force-push can redeploy old code or break the pipeline for everyone.
Rejections that aren't about history
remote: error: GH006: Protected branch update failed: the branch requires pull requests, reviews or passing checks. Push to a new branch and open a PR.! [remote rejected] main -> main (pre-receive hook declined): a server-side rule rejected the push. Common causes are a secret detected in the commit by push protection, a file over the size limit, or a commit message rule. Read theremote:lines above it.Permission denied (publickey): an SSH authentication problem, not a rejection. See my SSH permission denied guide.refusing to allow a Personal Access Token to create or update workflow: your token lacks theworkflowscope needed to change files in.github/workflows.
Habits that prevent it
- Pull before you start working, not only before you push.
- Work on short-lived branches and merge through pull requests. Two people rarely push to the same branch then.
- Protect
mainand let CI test every PR. My GitHub Actions deploy with auto-rollback deploys only what lands onmain, and rolls back if the health check fails. - Teach AI coding agents your rules. If Claude Code or Codex commits for you, put "never force-push main" in your AGENTS.md or CLAUDE.md.
Frequently asked questions
What does "non-fast-forward" mean in Git?
Your branch doesn't contain the latest commit on the remote branch, so the remote can't just move forward to your commit. Accepting the push would drop the remote's newer commits, so Git rejects it until you combine the two histories.
Should I use git pull or git pull --rebase?
Both are safe. Rebase keeps a straight history and is the common choice for personal local commits. Merge keeps the exact history and adds a merge commit. Teams usually pick one and set pull.rebase accordingly.
Is git push --force safe?
Not on a branch other people use, because it overwrites their commits. Use --force-with-lease, and only on your own branches after an intentional rewrite such as a squash or rebase.
Will git pull --rebase delete my changes?
No. Your commits are replayed on top of the remote ones. If something goes wrong, git rebase --abort returns you to where you started, and git reflog can recover any commit you had.
How do I fix "failed to push some refs to origin"?
Read the line above it. "rejected (non-fast-forward)" or "fetch first" means pull first. "remote rejected" means a server rule like branch protection or push protection blocked it, which the remote: lines explain.
Want pushes that deploy themselves, safely?
I set up Git workflows and GitHub Actions pipelines that test every change, deploy on merge and roll back automatically. See my CI/CD pipeline service, all DevOps services, or tell me how your team ships today.
Written by
MD Rakibul Islam Rakib
Full-stack developer, DevOps engineer and Linux system administrator with 5+ years of production experience. I deploy, harden and fix servers and web apps for clients worldwide, and everything in this article runs on real servers I manage, including this site.
- git push rejected
- non-fast-forward
- failed to push some refs
- git pull --rebase
- force-with-lease
- divergent branches
- GitHub


