Updates were rejected, non-fast-forward
Why git rejects a push as non-fast-forward, the safe way to pull and integrate first, and when force-with-lease is appropriate instead.
Last updated
! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'https://github.com/user/repo.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again.
What it means: The remote branch has commits your local branch doesn't have, so git refuses to push and silently overwrite them — it wants the remote history integrated first.
Likely causes
Most probable first — with how to confirm.
- 1.Someone else — or you, from another machine — pushed commits to the same branch after your last pull; run `git fetch && git log HEAD..origin/main --oneline` to see exactly what's missing locally.
- 2.You rewrote local history (rebase or amend) and the remote still has the old commits, so the two histories have genuinely diverged rather than one simply trailing the other.
- 3.You're pushing to the wrong branch by mistake — e.g. your local branch and CI both target `main` — check `git branch -vv` for what your branch is actually tracking.
Fixes
Safest first; destructive ones are called out.
- →Pull and integrate first, then push: `git pull --rebase origin main` (keeps history linear) or `git pull origin main` (merges), resolve any conflicts, and push again — the safe default.
- →If you deliberately rewrote history and are sure no one else needs the old commits, `git push --force-with-lease` is safer than a plain `--force` because it aborts if the remote changed since your last fetch.
- →Avoid a bare `git push --force` on a shared branch without confirming with collaborators — it can discard their commits outright.
- →If you pushed to the wrong branch, check its tracking target with `git branch -vv` and point it at the right one before pushing again.
Part by part
- [rejected]
- The push was refused by the remote. Nothing was transferred, so the remote is exactly as it was before you ran the command.
- (non-fast-forward)
- The reason: your branch is not a direct continuation of the remote's. Accepting it would require discarding commits that exist there and not here.
Common mistakes
git push --forceOverwrites the remote branch, discarding whatever someone else pushed in the meantime — silently, and with no record of what was lost. This is the command that costs colleagues an afternoon.
git push --force-with-leasegit pullFine when a merge commit is acceptable, and it produces one every time. On a branch where history should stay linear, git pull --rebase is what you meant.
Deleting the local branch and re-cloningDiscards your commits along with the problem. The remote work needs integrating, not avoiding — pull with rebase does exactly that and keeps both sides.
Questions
What is the difference between --force and --force-with-lease?
Plain force overwrites unconditionally. With-lease refuses if the remote has moved since you last fetched, so it protects against exactly the case where someone else pushed while you were working. There is little reason to use plain force.
Why does this happen right after I rebase?
Rebasing rewrites your commits with new hashes, so your branch is no longer a continuation of what the remote holds. That is expected, and it is the one legitimate case for force-with-lease.
How do I see what is on the remote that I do not have?
git fetch, then git log HEAD..origin/main lists the commits you are missing. Reading that before deciding how to integrate takes seconds and prevents most bad choices here.
Should force-pushing be blocked entirely?
On shared branches, yes — branch protection makes this class of accident impossible. On personal feature branches it is a normal part of rebasing, which is why the restriction usually applies only to main.