git remote add
git remote add is the command that teaches your local Git repository about a remote repository somewhere else — almost always a repository hosted on GitHub. Until you run it, your local repo is an isolated island: it has commits, branches, and a full history, but no idea where to push them or pull updates from. git remote add gives that remote a short, memorable name (by convention, origin) and a URL, so that every later git push, git pull, and git fetch can refer to it by name instead of typing the URL every time.
Overview / How it works
A “remote” in Git is nothing more than a named URL stored in your repository’s configuration file, .git/config. Git repositories don’t have a built-in concept of a single “central” server the way some older version control systems do — every clone is a complete, independent repository with its own full history. Remotes are simply a convenience layer: a lookup table mapping short names to the addresses of other repositories you want to exchange commits with.
When you run git remote add origin https://github.com/octocat/hello-world.git, Git does exactly one thing: it writes a new section into .git/config recording the name origin, the URL, and a default “refspec” that describes how remote branches map to local remote-tracking branches. Nothing is downloaded and no network connection is made. The remote repository isn’t even checked for existence at this point — that verification happens later, the first time you actually talk to it with git fetch, git pull, or git push.
The name origin is a convention, not a keyword. It’s just the name Git tools use by default when you clone a repository (git clone automatically creates a remote named origin pointing at the URL you cloned from). You could name a remote github, gitlab, or backup — Git doesn’t care. What matters is that the name is unique within your repository and that every command that needs to reach that remote (push, fetch, pull) uses the same name consistently.
Under the hood, Git’s object model is what makes remotes work at all. Every commit is an object that points to a tree (a snapshot of your project’s directory structure), and every tree points to blobs (raw file contents) and other trees (subdirectories). Commits, trees, and blobs are all identified by a SHA-1 hash of their content, so the exact same file content anywhere in the world hashes to the exact same blob ID. When you fetch or push, Git compares the object IDs it has against the object IDs the remote has, and transfers only the objects that are missing on one side — it does not need to “know” about the remote in advance beyond the URL git remote add gave it. A branch, whether local or remote, is just a movable pointer (a single line of text) to one of these commit objects; adding a remote is what lets Git ask “what does your main pointer point to?” over the network.
Syntax
git remote add "<remote-name>" "<remote-url>"
| Part / Option | Meaning |
|---|---|
<remote-name> |
The short name you’ll use to refer to this remote in later commands. origin is the near-universal convention for “the main remote you cloned from or push to”; upstream is the convention for “the original repository I forked from.” |
<remote-url> |
An HTTPS URL (https://github.com/user/repo.git) or an SSH URL (git@github.com:user/repo.git) pointing at the remote repository. |
-f |
Immediately runs git fetch against the new remote right after adding it, so its branches show up right away as remote-tracking branches. |
-t <branch> |
Limits fetching to a single specific branch instead of all branches on the remote. Can be repeated for multiple branches. |
--tags |
Imports all tags from the remote on every subsequent fetch (this is the default behavior already, but can be stated explicitly). |
--no-tags |
Prevents automatic tag import from this remote. |
Examples
Example 1: Connecting a brand-new local repository to GitHub over HTTPS
Say you started a project locally before creating anything on GitHub. After creating an empty repository on GitHub named hello-world, you connect the two:
git init
git remote add origin https://github.com/octocat/hello-world.git
git remote -v
Output:
origin https://github.com/octocat/hello-world.git (fetch)
origin https://github.com/octocat/hello-world.git (push)
git remote -v (“verbose”) lists every configured remote along with the URLs Git will use for fetching and pushing — they’re usually the same URL, but they’re tracked separately because Git technically allows them to differ. At this point nothing has been transferred; you’ve only recorded where origin lives.
Example 2: Connecting over SSH and pushing for the first time
SSH is the more convenient option if you push frequently, since it authenticates with a key pair instead of asking for a token on every push:
git remote add origin git@github.com:octocat/hello-world.git
git push -u origin main
Output:
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 225 bytes | 225.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:octocat/hello-world.git
* [new branch] main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
The -u (short for --set-upstream) flag does double duty: it pushes your commits and also records that your local main branch tracks origin/main, so future plain git push and git pull commands on this branch know where to go without repeating origin main every time.
Example 3: Adding a second remote for a fork workflow
A very common pattern on GitHub: you fork someone else’s repository, clone your fork (which becomes origin automatically), and then add the original repository as a second remote named upstream so you can pull in updates from it:
git remote add upstream https://github.com/original-owner/hello-world.git
git remote -v
git fetch upstream
Output:
origin https://github.com/octocat/hello-world.git (fetch)
origin https://github.com/octocat/hello-world.git (push)
upstream https://github.com/original-owner/hello-world.git (fetch)
upstream https://github.com/original-owner/hello-world.git (push)
remote: Enumerating objects: 12, done.
Unpacking objects: 100% (12/12), done.
From https://github.com/original-owner/hello-world
* [new branch] main -> upstream/main
A repository can have as many remotes as you like — there’s nothing special about the number two. git fetch upstream downloads the original repo’s objects and creates a local remote-tracking branch, upstream/main, without touching your own main branch at all. You’d typically follow this with something like git merge upstream/main or git rebase upstream/main to bring those changes into your own work.
How it works step by step
When you run git remote add <name> <url>, Git performs these steps:
- Checks that no remote with that name already exists in
.git/config. If one does, Git refuses and prints an error rather than silently overwriting it. - Writes a new
[remote "<name>"]section into.git/config, storing the URL underurl =and a default fetch refspec underfetch =(typically+refs/heads/*:refs/remotes/<name>/*, meaning “map every branch on the remote to a remote-tracking branch underrefs/remotes/<name>/“). - Does not contact the network, unless you passed
-f, in which case it immediately runs afetchafterward.
That refspec is what later commands rely on. When you subsequently run git fetch origin, Git opens a connection to the URL stored for origin, asks it for the SHA-1 of every branch and tag it has, compares those against the objects already in your local object database, downloads only the missing commits/trees/blobs, and then updates your remote-tracking branches (e.g. refs/remotes/origin/main) to point at the new commits. Your own local branches, like main, are never moved by a fetch — only a subsequent merge, rebase, or a pull (which is fetch + merge/rebase in one step) will move them.
Common Mistakes
Mistake 1: Trying to add a remote name that already exists
git remote add origin https://github.com/octocat/hello-world.git
Output (when origin is already configured):
error: remote origin already exists.
This happens constantly right after git clone, since cloning already creates origin for you, or when someone tries to “fix” a wrong URL by adding it again instead of updating it. git remote add only creates new remotes; it will never overwrite an existing one. The fix is to update the URL on the existing remote instead:
git remote set-url origin https://github.com/octocat/hello-world.git
If you genuinely want to start over, remove the remote first with git remote remove origin and then add it again.
Mistake 2: A typo in the URL that only surfaces later
git remote add origin https://github.com/octocat/helloworld.git
Output (on the next push, minutes or days later):
remote: Repository not found.
fatal: repository 'https://github.com/octocat/helloworld.git/' not found
Because git remote add never validates the URL, a typo (a missing hyphen, wrong username, wrong repo name) sits silently in your config until the first real network operation fails. Always sanity-check with git remote -v right after adding a remote, and consider running git ls-remote <name> once to confirm Git can actually reach it before you rely on it. Fix a wrong URL the same way as above:
git remote set-url origin https://github.com/octocat/hello-world.git
Best Practices
- Stick to the conventions: name your primary remote
originand, when working with a fork, name the original repositoryupstream. Consistent naming makes commands portable across projects and teammates. - Run
git remote -vimmediately after adding a remote to confirm the name and URL are exactly what you intended. - Prefer SSH URLs (
git@github.com:user/repo.git) for repositories you push to often, since GitHub no longer accepts password authentication over HTTPS — HTTPS instead requires a Personal Access Token on every credential prompt (though a credential helper can cache it) or an SSH key with no re-prompting at all. - Never paste a token or password directly into a remote URL (e.g.
https://ghp_xxxx@github.com/...); it gets stored in plain text in.git/configand can leak if you ever share that file or the repository. - Use
git remote set-urlto fix an existing remote’s URL, and reservegit remote addstrictly for genuinely new remotes. - When you no longer need a remote, remove it cleanly with
git remote remove <name>rather than leaving stale, unreachable URLs in your config. - If you’re setting up a fork workflow, add
upstreamright after cloning your fork, before you start making changes, so pulling in updates is a habit from day one.
Practice Exercises
- Create a new empty repository on GitHub (don’t initialize it with a README). Locally, run
git initin an empty folder, make one commit, then usegit remote addto connect it to your new GitHub repository over HTTPS, and push yourmainbranch. Confirm withgit remote -vthat the fetch and push URLs both point where you expect. - On GitHub, fork any public repository into your own account. Clone your fork locally, then add the original repository as a second remote named
upstream. Rungit fetch upstreamand inspect the newupstream/mainremote-tracking branch withgit log upstream/mainwithout merging anything yet. - Deliberately add a remote with a typo’d URL, watch
git pushfail with a “repository not found” error, then correct it usinggit remote set-urlinstead of removing and re-adding the remote. Verify the fix withgit remote -vbefore pushing again.
Summary
git remote add <name> <url>records a name-to-URL mapping in.git/config; it performs no network operation by itself.originandupstreamare naming conventions, not special keywords — you can name a remote anything, but consistency matters.- A repository can have any number of remotes at once, which is the basis of fork-and-pull-request workflows.
- Adding a remote with a name that already exists fails with an error; use
git remote set-urlto change an existing remote’s URL instead. - Remote URLs aren’t validated until you actually fetch, pull, or push, so double-check them with
git remote -vright away. - Prefer SSH URLs for frequent pushes, and never embed tokens or passwords directly in a remote URL.
