git remote
TL;DR: The git remote command manages the list of tracked external repositories (bookmarks) that your local repository synchronizes with. Rather than constantly writing raw network URLs, it maps aliases (like origin) to remote repositories. Git stores these definitions in the local .git/config file, allowing commands like git fetch, git push, and git pull to communicate with the outside world.
What Does git remote Actually Do?
To understand git remote, you must first grasp the core design philosophy of Git: distributed version control. Unlike centralized version control systems (like SVN or Perforce) where a single central server holds the entire history and clients only check out a single snapshot, a Git repository is fully self-contained. When you initialize a repository locally using git init, your computer holds the entire commit database, tree structures, and reference pointers inside the local .git/ directory.
However, software development is rarely a solo endeavor. To collaborate, you must sync your local commit history with histories hosted elsewhere—whether on a team member’s computer, a self-hosted server, or a hosting platform like GitHub, GitLab, or Bitbucket.
This is where the concept of a remote comes in. A remote is a bookmark, or an alias, that associates a human-readable label (such as origin or upstream) with a network locator (either an HTTPS URL or an SSH address).
+---------------------------------------+
| GitHub / GitLab Remote |
| URL: https://github.com/user/project |
+---------------------------------------+
^
| git push / git fetch
v
+-------------------------------------------------------------------------+
| Local Git Repository |
| |
| .git/config: |
| [remote "origin"] |
| url = https://github.com/user/project |
| |
| Local Refs: refs/heads/main ------> [Commit: 7a8f9c] |
| Remote Refs: refs/remotes/origin/main ------> [Commit: 7a8f9c] |
+-------------------------------------------------------------------------+
Instead of typing:
git push https://github.com/user/project.git main
You tell Git to save that URL under the name origin, and you can simply run:
git push origin main
A common misconception is that origin is a special, hardcoded keyword in Git. It is not. origin is simply the default alias Git assigns when you use git clone to copy a repository. You could rename it to github, server, or destination without affecting Git’s core logic.
Furthermore, git remote is purely a local metadata management tool. It does not perform active network communication with the remote server. Running git remote add or git remote set-url merely edits local text configuration files. Git only contacts the network when you execute network-reliant commands like git fetch (detailed in the /git-fetch tutorial), git push (detailed in the /git-push tutorial), or git pull (detailed in the /git-pull tutorial).
What Happens Inside .git/ (Hands-on exploration)
To understand how Git tracks remotes, let’s build a repository from scratch and watch how Git updates its internal metadata files.
Step 1: Initialize a Local Repository
First, let’s create a fresh directory and initialize a Git repository.
$ mkdir remote-demo
$ cd remote-demo
$ git init
Initialized empty Git repository in /home/admin/Code/remote-demo/.git/
If we inspect the .git/config file (which is handled globally and locally by /git-config), we see that it contains only basic configuration variables:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
At this stage, there are no remote tracking sections, and the .git/refs/ directory has no concept of remote branches:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
Step 2: Add a Remote Repository
Now, let’s add a remote named origin pointing to a mock repository URL:
$ git remote add origin https://github.com/octocat/hello-world.git
This command runs instantly because it does not attempt to connect to github.com. Let’s inspect the changes in .git/config now:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/octocat/hello-world.git
fetch = +refs/heads/*:refs/remotes/origin/*
A new block, [remote "origin"], has been appended. It contains two lines:
url = https://github.com/octocat/hello-world.git: The network location of the remote.fetch = +refs/heads/*:refs/remotes/origin/*: This is the Refspec (reference specification). It defines how local tracking references map to remote references.
Specifically, the refspec +refs/heads/*:refs/remotes/origin/* tells Git: “When fetching from this remote, take whatever branches are found under the remote’s refs/heads/ namespace, and mirror them locally in my own repository under refs/remotes/origin/.” The + sign permits Git to non-fast-forward update these refs during a fetch if necessary.
Let’s check the contents of .git/ now:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
Notice that .git/refs/remotes/ still does not exist! That is because we have not fetched any data yet.
Step 3: Simulating a Fetch and Analyzing Remote Refs
When you clone a repository with git clone (see /git-clone), or perform a git fetch on an existing remote, Git connects to the remote server, downloads objects, and populates the remote-tracking refs.
Let’s simulate what happens when a fetch occurs. Git creates a directory structure under .git/refs/remotes/<remote-name>/ and writes the downloaded commit hashes into plain text files representing the remote branches.
Suppose the remote repository has a branch named main pointing to commit c3bf1a98075f1591e1d3e8e1f584e03f905c31ff. After fetching, Git writes that hash into a file:
$ mkdir -p .git/refs/remotes/origin
$ echo "c3bf1a98075f1591e1d3e8e1f584e03f905c31ff" > .git/refs/remotes/origin/main
Now let’s list the directory tree:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
.git/refs/remotes
.git/refs/remotes/origin
.git/refs/remotes/origin/main
Let’s read the contents of this remote ref file:
$ cat .git/refs/remotes/origin/main
c3bf1a98075f1591e1d3e8e1f584e03f905c31ff
This file is a remote-tracking branch. It is a read-only pointer representing the state of the main branch on the remote server the last time you communicated with it. When you type git log origin/main, Git is not connecting to GitHub; it is simply opening .git/refs/remotes/origin/main to find the starting commit hash and traverse history.
If we run a plumbing command like git show-ref, Git outputs both local and remote refs:
$ git show-ref
c3bf1a98075f1591e1d3e8e1f584e03f905c31ff refs/remotes/origin/main
Step 4: Modifying the Remote with CLI Commands
When you run commands to modify the remote, Git simply updates the config file. For instance, let’s rename the remote from origin to upstream:
$ git remote rename origin upstream
Let’s inspect .git/config again:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "upstream"]
url = https://github.com/octocat/hello-world.git
fetch = +refs/heads/*:refs/remotes/upstream/*
And look at the directory structure under .git/refs/remotes/:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
.git/refs/remotes
.git/refs/remotes/upstream
.git/refs/remotes/upstream/main
Git automatically renamed the section in .git/config and renamed the corresponding subdirectory in .git/refs/remotes/ to preserve consistency!
Finally, let’s remove the remote:
$ git remote remove upstream
Let’s inspect .git/config one last time:
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
The config section is gone, and if we check .git/refs/remotes/, the tracking branch directories have been entirely cleaned up:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
This proves that git remote acts as a direct administrative tool for .git/config and the .git/refs/remotes/ namespace.
Detailed Command Options & Advanced Usage
While git remote add covers 90% of basic development, advanced team setups, open-source contributions, and deployment pipelines require a deeper command set.
1. Managing Multiple Remotes (Forks & Upstreams)
In collaborative open-source environments, you typically do not have direct write access to the main project repository (the “upstream” repository). Instead, you “fork” the repository on GitHub to create your own copy (the “origin” repository).
To synchronize changes, you add both remotes to your local workspace:
# Add your personal fork (read-write)
git remote add origin git@github.com:yourusername/project.git
# Add the primary project repository (read-only or merge-only)
git remote add upstream git@github.com:originalauthor/project.git
Running git remote -v shows how Git maps these names to distinct URLs:
$ git remote -v
origin git@github.com:yourusername/project.git (fetch)
origin git@github.com:yourusername/project.git (push)
upstream git@github.com:originalauthor/project.git (fetch)
upstream git@github.com:originalauthor/project.git (push)
Now, you can run git fetch upstream to download changes from the main project, merge them locally, and push them to your fork with git push origin main.
2. Deep Dive into Refspecs (Custom Fetching Rules)
The refspec controls exactly what Git fetches. By default, it retrieves all remote branches:
fetch = +refs/heads/*:refs/remotes/origin/*
However, if you are working with a massive repository containing thousands of developer feature branches, fetching everything might consume excessive bandwidth and disk space. You can modify the refspec to only track specific branches.
For example, to configure a remote to only track the main and release branches, you can configure .git/config like this:
# Delete the default broad refspec
git config --unset-all remote.origin.fetch
# Add explicit branch mappings
git remote set-branches origin main release
This updates your configuration to restrict downloads to only these branches:
[remote "origin"]
url = https://github.com/octocat/hello-world.git
fetch = +refs/heads/main:refs/remotes/origin/main
fetch = +refs/heads/release:refs/remotes/origin/release
3. Pushing to Multiple URLs Simultaneously
If you have a deployment flow where you want to push your code to two separate hosts (for example, GitHub and a backup server or a PaaS platform like Heroku) with a single command, you can add multiple push URLs to a single remote alias.
Using git remote set-url --add --push, you modify the push target:
# Set the primary push URL
git remote set-url --add --push origin git@github.com:user/project.git
# Add a secondary backup push URL
git remote set-url --add --push origin git@backup-server.com:user/project.git
This modifies .git/config as follows:
[remote "origin"]
url = https://github.com/user/project.git
fetch = +refs/heads/*:refs/remotes/origin/*
pushurl = git@github.com:user/project.git
pushurl = git@backup-server.com:user/project.git
When you run git push origin main, Git will establish SSH connections to both destinations sequentially and upload the commits.
Real-World Scenario: Syncing a Forked Repository
Let’s look at a common software engineering workflow: contributing to an open-source project. You have a fork of a repository, and the original repository (upstream) has moved forward with new commits. You need to pull those changes into your local workspace, resolve conflicts, and update your remote fork.
Step 1: Check Current Remotes
You start by listing your current configurations:
$ git remote -v
origin git@github.com:developer-jane/library.git (fetch)
origin git@github.com:developer-jane/library.git (push)
Step 2: Connect the Upstream Repository
You add a new remote pointer targeting the official parent repository:
$ git remote add upstream git@github.com:organization/library.git
Verify that the remote list updated:
$ git remote -v
origin git@github.com:developer-jane/library.git (fetch)
origin git@github.com:developer-jane/library.git (push)
upstream git@github.com:organization/library.git (fetch)
upstream git@github.com:organization/library.git (push)
Step 3: Fetch the Upstream History
You fetch the latest commits from the upstream server. This creates the remote-tracking pointers in .git/refs/remotes/upstream/:
$ git fetch upstream
From github.com:organization/library
* [new branch] main -> upstream/main
* [new branch] dev -> upstream/dev
Step 4: Rebase Your Feature Branch
You switch to your local feature branch and rebase it onto the newly updated upstream main branch:
$ git checkout feature-login
$ git rebase upstream/main
Successfully rebased and updated refs/heads/feature-login.
Step 5: Push Your Updated Code to Your Fork
Now you push the updated feature branch to your fork (origin), which you can then use to open a Pull Request to the upstream organization:
$ git push origin feature-login
Common Pitfalls and Mistakes
1. The “Out of Sync” Remote Myth
- The Problem: A developer runs
git log origin/mainto view changes made by their coworker, but nothing shows up, even though the coworker pushed their code hours ago. - The Cause: Git is entirely local. Running
git log origin/mainsimply reads the local ref file.git/refs/remotes/origin/main. If you haven’t rungit fetch, that file remains stuck on the old commit hash. - The Fix: Run
git fetch originfirst to update your local copy of the remote refs before reviewing history.
2. Protocol Mismatch (HTTPS vs SSH) and Auth Failures
- The Problem: You receive authentication errors when attempting to run
git push origin mainbecause your terminal environment does not have credentials configured for the protocol defined in the URL. - The Cause: You cloned via HTTPS (
https://github.com/...) which prompts for username/password (which GitHub no longer accepts in favor of Personal Access Tokens), but you want to use SSH keys (git@github.com:...). - The Fix: Swap the remote URL to SSH:
git remote set-url origin git@github.com:username/repository.git
3. Confusing git remote rm with Remote Branch Deletion
- The Problem: A developer wants to delete a remote branch named
feature-deprecatedon GitHub. They rungit remote rm feature-deprecated. - The Cause:
git remote rmdeletes the reference to the entire remote host definition (likeoriginorupstream) in.git/config. It does not delete individual branches on a server. - The Fix: To delete a specific branch on a remote server, use:
If you accidentally rangit push origin --delete feature-deprecatedgit remote rm origin, you can recreate the remote by runninggit remote add origin <url>.
Command Quick Reference
| Command | Action | Scope / Effect |
|---|---|---|
git remote | Lists the names of all configured remotes. | Local |
git remote -v | Lists all configured remotes with their fetch and push URLs. | Local |
git remote add <name> <url> | Adds a new remote reference to .git/config. | Local |
git remote remove <name> | Deletes the specified remote configuration and its local tracking branches. | Local |
git remote rename <old> <new> | Renames a remote and updates its associated tracking refs folder. | Local |
git remote set-url <name> <new-url> | Updates the URL for a remote configuration. | Local |
git remote set-url --add --push <name> <url> | Appends a push URL to a remote for multi-destination pushes. | Local |
git remote show <name> | Connects to the remote server and displays a detailed report on tracking state. | Network |
git remote prune <name> | Deletes local tracking refs (under .git/refs/remotes/) that have been deleted on the server. | Local |
FAQ (People Also Ask)
Can a Git repository have multiple remotes?
Yes. Since Git is a distributed version control system, a repository is completely decoupled from any single server. You can configure as many remotes as you like. This is standard practice in open-source development (where you track the original project as upstream and your personal copy as origin) and in staging/deployment environments (where you might track a staging server as staging and a production server as production).
What is the difference between origin and upstream?
These are standard naming conventions rather than functional keywords in Git. origin generally refers to your own fork of a project (a copy hosted under your personal account on GitHub). upstream refers to the original repository from which you forked the project. You fetch changes from upstream to stay synchronized with the core project, and you push changes to origin to prepare pull requests.
How do I change a remote URL from HTTPS to SSH?
You can update your URL with the set-url command. First, find your SSH URL on GitHub (it will format like git@github.com:username/repo.git). Then, execute:
git remote set-url origin git@github.com:username/repo.git
This instantly replaces the HTTP address in your .git/config file.
Why does git remote show origin take several seconds to run, while git remote is instantaneous?
The git remote command merely lists the aliases defined inside the local text file .git/config. It is instantaneous because it requires no internet access. On the other hand, git remote show <name> connects directly to the remote server over the network to compare your local branch list with the current list on the remote server, reporting which branches are out of date, merged, or ready to pull.
Test Your Knowledge
Loading quiz…