Git "! [rejected] main -> main (fetch first)": why the push failed and how to fix it safely
Fix git push "rejected (fetch first)" and "non-fast-forward": pull with rebase or merge, use --force-with-lease instead of --force, and handle protected branches.
by OhSudo ··
Part of a learning path:Git for everyday work
You push, and Git refuses:
$ git pushTo github.com:example/app.git ! [rejected] main -> main (fetch first)error: failed to push some refs to 'github.com:example/app.git'hint: Updates were rejected because the remote contains work that you do nothint: have locally. This is usually caused by another repository pushing tohint: the same ref. If you want to integrate the remote changes, usehint: 'git pull' before pushing again.The remote branch has commits you don’t have — a colleague pushed, you committed from another computer, or someone merged a pull request in the web interface. A push may only add commits on top of what’s there; it would have to throw those commits away, so Git stops. Nothing is broken, and nothing is lost yet. Don’t make it worse with --force.
- Your mainA → B → your commitpush refused(fails)
- Remote mainA → B → a colleague's commit(breaks here)fix
- git pull, then pushyours on top of theirs(works)
TL;DR
git pull --rebase # put your commits on top of the remote onesgit pushIf the rebase stops with a conflict: fix the files, git add them, git rebase --continue. To give up and go back: git rebase --abort.
1. See what’s different
You can try this in the terminal, where your push has just been rejected:
git fetchgit log --oneline main..origin/main # commits on the remote that you don't havegit log --oneline origin/main..main # your commits that the remote doesn't havegit fetch only downloads; it doesn’t touch your files. Now you know what you’re about to combine.
The same rejection can also look like this:
! [rejected] main -> main (non-fast-forward)hint: Updates were rejected because the tip of your current branch is behindhint: its remote counterpart.Same cause, same fix — Git just already knew about the remote commits.
2. Combine: rebase or merge
Rebase replays your commits on top of the remote ones. History stays a straight line — what most teams prefer for small local work:
Merge keeps both lines and adds a merge commit. Use it when your branch is shared with others, or when the team prefers it:
If a plain git pull answers with “You have divergent branches and need to specify how to reconcile them”, Git wants you to choose once. Make rebase the default:
git config --global pull.rebase true3. Conflicts
If you and the remote changed the same lines, Git stops and marks the files:
CONFLICT (content): Merge conflict in config/app.ymlgit status lists them. Open each file, keep the right version between the <<<<<<<, ======= and >>>>>>> markers, remove the markers, then:
git add config/app.ymlgit rebase --continue # or: git commit, if you were mergingChanged your mind? git rebase --abort (or git merge --abort) puts everything back as it was before the pull.
4. When you really do need to force
After you rewrite commits you already pushed (git commit --amend, an interactive rebase, a squash), the remote has the old versions and you have the new ones. A normal push is rejected, and here overwriting is the point — but do it carefully:
git push --force-with-lease--force-with-lease overwrites the remote only if it’s still where you last saw it. If a colleague pushed in the meantime, it refuses instead of silently deleting their work — plain --force would delete it.
One catch: an IDE that runs git fetch in the background updates “where you last saw it”, and the protection is gone. Git 2.30+ adds a check for that: git push --force-with-lease --force-if-includes.
Warning
Never force-push a branch other people work on (main, develop) unless the whole team agreed. Their next pull will be a mess, and their commits can disappear from the remote.
5. Protected branches
If the branch is protected on the server, the push fails even when it’s a normal one:
remote: error: GH006: Protected branch update failed for refs/heads/main.On GitLab: You are not allowed to push code to protected branches on this project. The fix isn’t in Git: push to a new branch and open a pull request (merge request):
git switch -c fix/login-timeoutgit push -u origin fix/login-timeoutShell, we have a problem!
Similar, but different: fatal: refusing to merge unrelated histories — the local repository and the remote one started separately (typically: you ran git init locally and created the repository on GitHub with a README). Combine them once with git pull origin main --allow-unrelated-histories. Permission denied (publickey) on push is an SSH key problem — see SSH “Permission denied (publickey)”. If the two sides share no history at all, see refusing to merge unrelated histories. If git pull stops because your local changes would be overwritten, see that article. If Git says your branch doesn’t exist, see src refspec main does not match any. All the everyday commands are in the Git Cheat Sheet. RPC failed / the remote end hung up unexpectedly is a push that broke off in transit.
$ ./practice.sh
Practice this in the terminal
Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.