Cloud Explainers
Home › Git

How to Fix Git 'Updates Were Rejected' When You Push

Git7 min videoPublished Nov 14, 2026

git push says '! [rejected] ... (fetch first)' or '(non-fast-forward)': updates were rejected. On a real Git server and two laptops we made it happen for real and fixed it: why the server refuses (a non-fast-forward would lose someone's commit), why git status still said 'ahead by 1', why plain git pull stops with 'Need to specify how to reconcile divergent branches', and the fix: git pull --rebase, then push. Plus the two other common cases: after an amend (git push --force-with-lease) and a new repository created with a README (refusing to merge unrelated histories).

Updates were rejected

You typed git push, and Git said: updates were rejected. Here's the same push from the same laptop, a minute later: accepted, and nothing lost: cat prints both files, with Ben's Friday show and Ana's tour times, on the radio station's website. So what changed?

ana@ana-laptop: Ana's laptop
git pushTo git-server:station.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'git-server:station.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
ana@ana-laptop: Ana's laptop
git pushEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 367 bytes | 367.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git-server:station.git
   c26da60..221f69e  main -> main
ana@ana-laptop: Ana's laptop
cat schedule.html about.html<h1>Station schedule</h1>
<p>Mon 7pm: Local Bands</p>
<p>Wed 8pm: Jazz Hour</p>
<p>Fri 9pm: Night Folk</p>
<h1>About the station</h1>
<p>Run by volunteers since 1998.</p>
<p>Studio tours: Saturdays at 11.</p>

A minute earlier, the same push got this. Rejected. Main to main: Ana's branch, onto the server's. And the reason: fetch first. The server has work her laptop doesn't. The hint says git pull, but on a fresh install, plain git pull refuses too.

One line of Git's answer: diagram of ! main -> main (fetch first), = refused, main -> main = branch to branch, (fetch first) = the reason, Hint: git pull. On a fresh install, plain git pull refuses too

Why the server refuses

Here's what happened. Ana and Ben started from the same first version. Ben added the Friday show and pushed it, so the server's main branch now ends at Ben's commit. Ana added the tour times on her laptop, on top of the old version. A push asks the server: move main to my commit. But Ana's commit doesn't contain Ben's. Moving main there would drop Ben's work from the branch, so the server refuses.

Why the server refuses Ana's push: diagram of First version, Ben: Friday show, main, Ana: tour times, git-server, Ana's laptop

The quick fix: pull with rebase, then push

The usual fix is one command before the push: git pull --rebase. It fetches Ben's commit, then lifts Ana's commit off and replays it on top. Her commit gets a new ID, because its parent changed. git log draws the history, one commit per line with its short ID: one straight line again. Now the push only adds a commit, and the server accepts it.

ana@ana-laptop: Ana's laptop
git pull --rebaseSuccessfully rebased and updated refs/heads/main.
ana@ana-laptop: Ana's laptop
git log --oneline --graph* 221f69e (HEAD -> main) Add studio tour times
* c26da60 (origin/main) Add the Friday folk show
* e8f5ad2 First version of the site

Think of a shared paper logbook. You wrote your entry on a copy at home. Meanwhile, someone wrote on the next line of the real book. You don't tear their page out. You read their line, then write yours under it. That's a rebase, then a push.

A shared paper logbook: diagram of Your copy at home, The real logbook, Tear their page out, Write yours under it

Git's own manual names the rule. A push that only adds commits on top is a fast-forward, and it loses nothing. A non-fast-forward update will lose history, so by default, Git refuses it. Every rejection hint in this video points to this note.

The quick fix: pull with rebase, then push: browser screenshot of only adds on top, loses history

What your laptop didn't know

Back to the moment of the rejection. Ana asks git status. Guess first: what does it say? Ahead by one commit. Nothing about Ben. Your laptop only knows what the server had at your last fetch or pull, and git status never asks the server.

ana@ana-laptop: Ana's laptop
git statusOn branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

git fetch downloads what the server has, without changing Ana's files. Now status tells the truth: diverged, one commit each. And the graph shows the fork: Ben's commit on origin main, Ana's on her own main, both growing from the first version.

ana@ana-laptop: Ana's laptop
git fetchremote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 313 bytes | 313.00 KiB/s, done.
From git-server:station
   e8f5ad2..c26da60  main       -> origin/main
ana@ana-laptop: Ana's laptop
git statusOn branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" if you want to integrate the remote branch with yours)

nothing to commit, working tree clean
ana@ana-laptop: Ana's laptop
git log --oneline --graph --all* c8c3798 (HEAD -> main) Add studio tour times
| * c26da60 (origin/main) Add the Friday folk show
|/
* e8f5ad2 First version of the site

Pushing now gives a second wording: non-fast-forward, the tip of your branch is behind. Fetch first means Git hadn't seen the server's new commit yet. Non-fast-forward means it has, and your branch still doesn't include it. Same cause, same fix.

Two wordings, one cause: comparison table of (fetch first) vs (non-fast-forward), Git's hint, When, Your branch, Fix

After you rewrote a pushed commit

Another way to get rejected: rewriting a commit you already pushed. Ana changes eleven to eleven A M, and fixes her last commit: dash dash amend replaces it, and no edit keeps its message. That makes a new commit to replace the one on the server, so status says diverged, and the push is rejected again. Here, a pull is the wrong fix: it would try to combine the old commit with its own replacement. Instead: git push --force-with-lease.

ana@ana-laptop: Ana's laptop
git commit -a --amend --no-edit[main 7abbfb8] Add studio tour times
 Date: Sat Nov 14 14:13:27 2026 +0000
 1 file changed, 1 insertion(+)
ana@ana-laptop: Ana's laptop
git statusOn branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" if you want to integrate the remote branch with yours)

nothing to commit, working tree clean
ana@ana-laptop: Ana's laptop
git pushTo git-server:station.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'git-server:station.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.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Force with lease replaces the server's commit only if the server still has exactly what Ana last saw: origin main, as her last fetch or push recorded it. Here it does, so the push goes through: forced update. If Ben had pushed in the meantime, it would refuse, where a plain force would wipe out his commit: tearing his page out of the logbook. Use it only on commits nobody else has built on.

ana@ana-laptop: Ana's laptop
git push --force-with-leaseEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 371 bytes | 371.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git-server:station.git
 + 221f69e...7abbfb8 main -> main (forced update)

Why plain git pull refused

So why did plain git pull refuse? Git can join the two lines in two ways, and recent versions won't pick one for you. It stops with: need to specify how to reconcile divergent branches, and lists three settings. Nothing was changed. It's a question, not a failure.

ana@ana-laptop: Ana's laptop
git pullhint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

The two ways. Merge keeps both lines and ties them together with an extra merge commit. Rebase replays your commits on top, for a straight line. For a few commits of your own that nobody else has yet, rebase is the tidy choice, so Ana makes it her default, once: git config dash dash global pull dot rebase true. And if you both changed the same line, either way stops at a conflict, which our merge conflicts video fixes step by step.

Two ways to join the lines: comparison table of git pull --no-rebase (merge) vs git pull --rebase, History, Your commits, Best for, Make it the default, Same line changed

A new repository with a README

One more, very common on a first push. Ana starts a new playlist project on her laptop, with its own first commit, and adds the server as origin, the usual name for it. The empty repository on the server was created with a README, as hosting sites offer. So the first push, with dash U to remember origin for next time, is rejected: fetch first. And a pull that merges stops with: refusing to merge unrelated histories, because the two first commits share no parent at all.

ana@ana-laptop: Ana's laptop
git push -u origin mainTo git-server:playlist.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'git-server:playlist.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
ana@ana-laptop: Ana's laptop
git pull --no-rebase origin mainwarning: no common commits
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 197 bytes | 197.00 KiB/s, done.
From git-server:playlist
 * branch            main       -> FETCH_HEAD
 * [new branch]      main       -> origin/main
fatal: refusing to merge unrelated histories

Ana's pull now rebases, the default she set earlier. It replays her playlist commit on top of the README, so both files are there, and the push goes through. git pull dash dash allow unrelated histories also works, joining the two with a merge commit, but on a brand-new project, rebase keeps the history one straight line.

ana@ana-laptop: Ana's laptop
git pull origin mainFrom git-server:playlist
 * branch            main       -> FETCH_HEAD
Successfully rebased and updated refs/heads/main.
ana@ana-laptop: Ana's laptop
lsREADME.md  tracks.txt
ana@ana-laptop: Ana's laptop
git push -u origin mainEnumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 10 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 294 bytes | 294.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git-server:playlist.git
   e022c62..67dd5a3  main -> main
branch 'main' set up to track 'origin/main'.

Recap

So, updates were rejected: the server has commits you don't. Pull with rebase, then push. If plain pull asks how, set pull.rebase true. If you rewrote a pushed commit, force with lease, never a plain force.

Updates were rejected: what to do: diagram of The server has new commits, Pull with rebase, then push, Pull asks how? Choose once, Rewrote a pushed commit

If your push went through, a like helps the next stuck beginner find this, and subscribing brings more fixes like this one, tried for real.

Every rejection in this video happened on a real Git server and two real laptops, and every command and the lab files are linked in the description. Thanks for watching Cloud Explainers.

Sources

Demos: containers on our own Mac, with our own Git server standing in for GitHub; the radio station and its volunteers are made up. Every push, pull and rejection was real. Watch the full video on YouTube.

More videos

Networking11 min

Every Networking Term a Beginner Needs in 12 Minutes

Docker6 min

How to Fix Docker 'Permission Denied' on docker.sock

Linux7 min

How to Fix nginx 502 Bad Gateway Errors on Linux

Linux8 min

How to Fix Linux apt 'Could Not Get Lock' Errors (Ubuntu)

Networking9 min

How Email Works on the Internet (SMTP) in 10 Minutes

Python8 min

The Python Roadmap for Beginners

Go (Golang)21 min

Golang REST API with Gin: Full Tutorial for Beginners

Networking10 min

How a WireGuard VPN Network Works

Linux9 min

Linux Makefiles Will Finally Make Sense After This

Auth (OAuth, JWT)9 min

SAML vs OAuth vs OpenID Connect: What's the Difference?

Python7 min

How to Fix Python cron Jobs That Never Run

Linux22 min

Linux Bash Scripting: Full Tutorial for Beginners

Networking10 min

How PXE Network Boot Works

Active Directory9 min

How Active Directory Works

Linux10 min

How to Fix a Linux systemd Service That Won't Start

Linux9 min

Linux Logs: syslog vs journald

Auth (OAuth, JWT)9 min

JWT vs Session Cookies: Which One Should You Use?

Docker10 min

Docker vs Docker Compose: Which One Should You Use?

Docker9 min

How to Fix Docker Compose Containers That Can't Talk to Each Other

Django (Python web framework)22 min

Django Full Tutorial for Beginners

Linux9 min

How Linux systemd Works

Networking8 min

How to Fix 'Windows Cannot Access' Network Share Errors (SMB)

APIs10 min

REST vs gRPC: Which One Should You Use?

Certificates & PKI (ADCS)9 min

How to Fix SSL Certificate Chain Errors (Missing Intermediate)

Python9 min

Every Python Concept for Beginners in 10 Minutes

Databases & SQL9 min

Every SQL Join Type in 10 Minutes

OpenRouter (one API for many AI models)9 min

Why OpenRouter Free Models Hit Their Limit So Fast

Gemini API & Google AI Studio9 min

Why Your Gemini API Free Tier Runs Out So Fast

OpenRouter (one API for many AI models)10 min

OpenRouter Will Finally Make Sense After This

Caching & Redis10 min

How Redis Caching Works

Gemini API & Google AI Studio10 min

Gemini API vs Vertex AI: Which One Should You Use?

Gemini API & Google AI Studio8 min

How to Fix a Leaked Gemini API Key

Go (Golang)9 min

Why Golang Uses Goroutines Instead of Threads

Gemini API & Google AI Studio9 min

How to Fix Gemini API Error 429: Resource Exhausted

Claude & the Claude Developer Platform10 min

Claude Token Pricing Will Finally Make Sense After This

Python11 min

Why Python Web Scraping Returns Nothing (and the Fix)

Python10 min

Python uv vs pip: Which One Should You Use?

Python22 min

Python Automation: Full Tutorial for Beginners

Auth (OAuth, JWT)9 min

How OAuth Login Works

Gemini API & Google AI Studio10 min

Google AI Studio Will Finally Make Sense After This

Docker10 min

What Happens When You Run a Docker Container

Claude & the Claude Developer Platform8 min

How to Fix Claude Code Billing Your API Key Instead of Your Plan

Auth (OAuth, JWT)9 min

Why Passkeys Can't Be Phished Like Passwords

PowerShell10 min

PowerShell vs Bash: Which One Should You Learn?

Linux10 min

The Linux Roadmap for Beginners

Django (Python web framework)9 min

Django vs FastAPI: Which One Should You Use?

Databases & SQL10 min

Why SQLite Is the Most Used Database in the World

Databases & SQL11 min

How a NoSQL Database Like MongoDB Works

Python11 min

How to Fix Python pip error: externally-managed-environment

Databases & SQL9 min

How to Fix MySQL Too Many Connections Database Errors

Go (Golang)9 min

Golang Concurrency Will Finally Make Sense After This

AWS10 min

How Amazon S3 Works

Kubernetes10 min

How Kubernetes Works

Git10 min

How Git Works

Docker10 min

How to Fix Docker Port Already Allocated Errors

PostgreSQL10 min

How PostgreSQL Works

Git8 min

How to Fix Git Merge Conflicts

Linux9 min

How to Fix No Space Left on Device in Linux

Networking10 min

How the Internet Works

Databases & SQL9 min

SQL vs NoSQL: When Each One Wins

Auth (OAuth, JWT)8 min

How to Fix JWT Token Expired Errors (401 Unauthorized)

Claude & the Claude Developer Platform9 min

Claude API vs Claude Pro: Why Your Subscription Doesn't Include It

AI agent skills9 min

Why AI Agent Skills Beat Giant Prompts

Linux10 min

Linux Cron Jobs Will Finally Make Sense After This

Cybersecurity10 min

Security Logs Will Finally Make Sense After This

Cybersecurity8 min

How to Fix a Security Leak: An API Key Pushed to GitHub

DNS9 min

DNS Will Finally Make Sense After This

Cybersecurity9 min

Never Ignore a Security Update

Auth (OAuth, JWT)9 min

Two-Factor Authentication Will Finally Make Sense

Networking8 min

Never Expose a Database to the Internet

Cybersecurity10 min

SSH Keys vs Passwords: Which Is Safer for Security?

Linux10 min

The 5 Levels of Linux Boot: From Power On to Login

Networking12 min

The 5 Levels of Network Firewalls

Cybersecurity10 min

Password Security: Why Hackers Crack Weak Passwords

Linux9 min

Why Your Linux Server Runs Out of Memory

Networking10 min

HTTP vs HTTPS: What Actually Changes on Your Network

Linux12 min

Every Linux File Permission You Need to Know

Linux10 min

Linux Processes vs Threads: When Each One Wins

Networking11 min

The OSI Model Will Finally Make Sense After This

Git11 min

The 7 Levels of Git

Networking10 min

Every Network Troubleshooting Command You Need

Cybersecurity10 min

Hashing vs Encryption in Cybersecurity: When Each One Wins

Networking11 min

Why the Internet Ran Out of IP Addresses

Linux11 min

Linux Package Managers: apt vs Snap vs Flatpak

Python9 min

Why Python Is Slow (and When It Doesn't Matter)

Networking11 min

TCP vs UDP: When Each One Wins

Linux8 min

Stop Running Linux as Root (Do This Instead)

Linux11 min

Linux Users and Groups Will Finally Make Sense

Cybersecurity9 min

Why Phishing Beats Good Security (and How to Spot It)

Docker12 min

The 7 Levels of Docker

AI agents & MCP12 min

AI Agents Will Finally Make Sense After This

Git8 min

Stop Using git push --force (Use This Instead)

AI agent skills10 min

Agent Skills vs MCP: When Each One Wins

Networking12 min

Networking Will Finally Make Sense After This

Linux12 min

Every Linux Command You Actually Need

Cybersecurity12 min

The 7 Levels of Cybersecurity

System design12 min

Every System Design Concept You Need, in One Diagram

PostgreSQL10 min

Why PostgreSQL Ignores Your Index