git push origin main means: send my local main branch to the remote named origin, and update the branch called main there. origin is a nickname for a URL, not a keyword. main appears twice implicitly — source and destination — which is why git push origin main:some-other-branch is legal and occasionally useful.
Most push failures are one of four things, and each has a distinct message. Once you can read which of the three parts of the command is being rejected, the fix is mechanical.
Table of contents
- What origin actually is
- main vs master
- The -u flag, and why the second push is shorter
- Rejected: fetch first
- Protected branches and permission errors
- Pushing a branch to a different name
- What a push should trigger
- How this fits the rest of the stack
- FAQ
What origin actually is
origin is a local alias for a URL. It is conventional, not magic — you can rename it, have several, or not have one at all.
git remote -v
# origin git@github.com:acme/billing.git (fetch)
# origin git@github.com:acme/billing.git (push)
# Add a second remote
git remote add staging git@github.com:acme/billing-staging.git
git push staging main
This is why git push origin main fails with 'origin' does not appear to be a git repository on a fresh git init — there is no remote yet. Add one with git remote add origin <url> and the same command works.
main vs master
Git’s default initial branch was master for most of its life and is main on new repositories created through GitHub and on Git installations configured that way. Nothing else changed; it is a name.
# What branch am I actually on?
git branch --show-current
# Set the default for new repos you create
git config --global init.defaultBranch main
# Rename an existing local branch and push it
git branch -m master main
git push -u origin main
error: src refspec main does not match any almost always means you are on master and pushing main, or the branch has no commits yet. Both are the same class of mistake: the source ref does not exist locally.
The -u flag, and why the second push is shorter
git push -u origin main sets the upstream, recording that local main tracks origin/main. After that, bare git push and git pull know where to go.
git push -u origin main # first time
git push # every time after
# Check what tracks what
git branch -vv
# * main c3d4e5f [origin/main] Fix the header
Without it you get fatal: The current branch main has no upstream branch, which is a prompt rather than a problem — Git even prints the exact command to run.
Rejected: fetch first
The most common real failure. Someone pushed while you were working, so the remote has commits your local branch does not.
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:acme/billing.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
Git is protecting the other person’s commits. Bring them down first, then push.
git pull --rebase origin main # replay your commits on top of theirs
git push origin main
# Or with a merge commit instead of a rebase
git pull origin main
git push origin main
--rebase gives a linear history and is usually nicer on a shared branch. --ff-only is the strict option — it refuses rather than creating a merge you did not intend, which some teams prefer as the default.
What you should not do is reach for --force here. It resolves the error by deleting the other person’s commits, which is a solution the way unplugging the smoke alarm is a solution.
Protected branches and permission errors
Two different failures look similar and are not.
Permission denied (publickey)— SSH authentication. Your key is not loaded or not on the account. Check withssh -T git@github.com.remote: Permission to acme/billing.git denied to <user>— you authenticated fine but lack write access to that repository.protected branch hook declined— you have access, but the branch requires a pull request. This is a policy, not a bug; open the PR.
The first is a key problem, the second is an access problem, the third is a process working as designed. Reading which one you got saves regenerating keys you did not need to touch.
Pushing a branch to a different name
The full form is git push <remote> <source>:<destination>. When you write git push origin main, Git expands it to main:main. Spelling both out is occasionally useful.
# Push local main to a remote branch called release
git push origin main:release
# Delete a remote branch (push nothing to it)
git push origin :old-feature
# or, more readably
git push origin --delete old-feature
The empty-source deletion syntax is one of Git’s less obvious jokes. --delete does the same thing and reads like English.
What a push should trigger
On a connected repository the push is the start of something, not the end. A build runs, and either a new version goes live or it does not.
The useful shape is that the push produces visible stages — build started, image created, route live — rather than a spinner and a hope. Good deployment UX explains what is happening; a failed deploy should tell you which step failed and leave the previous version serving traffic.
That is how RunxBuild handles a push from a connected GitHub repository: build logs for the attempt, a live route when it succeeds, the previous deploy still there to roll back to when it does not.
How this fits the rest of the stack
Read the command as three separate things and the errors sort themselves. origin is a URL alias — fix it with git remote. main is a branch name — check it with git branch --show-current. The rejection messages distinguish between “you are behind” (pull first), “you cannot authenticate” (key problem), and “you are not allowed” (access or branch policy).
Once the push lands, the interesting question is what it deploys. If you are sizing that up, the RunxBuild hosting calculator lays out the service, database, storage, and bandwidth as separate numbers rather than a single figure to squint at.
Useful related references:
- git push -f: When It Is Fine, When It Is Not, and the Safer Flag
- MongoDB push: Append to Arrays Without Growing Documents Forever
- How to Rename a File in Linux: mv, rename, and git mv
- Services on RunxBuild
FAQ
What does origin mean in git push origin main?
origin is a local nickname for a remote repository URL, created automatically when you clone. It is a convention, not a reserved word — run git remote -v to see the URL behind it, and you can add other remotes under any name you like and push to them the same way.
Why do I get ‘src refspec main does not match any’?
The local branch you named does not exist or has no commits. Usually you are on master and pushing main, or the repository is fresh and nothing has been committed yet. Check with git branch --show-current and git log --oneline -1, then push the branch that actually exists.
What does the -u flag do in git push -u origin main?
It sets the upstream tracking relationship so local main remembers it corresponds to origin/main. After that, plain git push and git pull work without arguments. You only need it once per branch; git branch -vv shows what is currently tracking what.
How do I fix ‘Updates were rejected because the remote contains work’?
The remote has commits you do not have locally. Run git pull --rebase origin main to replay your commits on top, then push again. Do not use --force to get past this message — it resolves the conflict by discarding whatever the other person pushed.
What is the difference between git push origin main and git push?
They are the same once an upstream is set. Bare git push uses the tracking information for the current branch; the explicit form names the remote and branch directly. Before the upstream exists, the explicit form with -u is what creates it.