Connecting a Local Repo to GitHub

Once you have a Git repository on your machine, GitHub only becomes useful when the two are linked together. Connecting a local repo to GitHub means telling Git the network address of a remote copy of your project, so commands like git push and git pull know where to send and fetch history. This lesson covers creating a matching repository on GitHub, wiring up the connection over HTTPS or SSH, pushing your first commits, and fixing the connection when it points at the wrong place or gets rejected.

Overview: How Local and Remote Repositories Connect

A Git repository does not know about GitHub by default. Every repository you create with git init is entirely self-contained — it has no idea that a hosted copy exists anywhere. What links the two is a remote: a name (almost always origin by convention) that Git associates with a URL. That mapping is stored as plain text in the repository’s own config file, .git/config. Once a remote exists, Git can push your local commits to it and fetch commits from it, but the relationship is just data in a config file, not anything magical or GitHub-specific — the same mechanism works with GitLab, Bitbucket, or a server you run yourself.

To understand what actually travels over the network when you connect and push, it helps to recall Git’s object model. Every commit you make points to a tree object, which is a snapshot of your project’s directory structure; the tree in turn points to blob objects, which store the raw content of each file, and to other trees for subdirectories. Each object is identified by the SHA-1 hash of its content, and a branch like main is nothing more than a small file containing the hash of its latest commit — a movable pointer, not a container of commits. When you run git push, Git figures out which commit, tree, and blob objects exist in your local history but are missing on the remote, bundles exactly those objects into a compressed packfile, and asks the remote to update its own main pointer to your new commit. Nothing is re-uploaded that the remote already has, which is why pushing a small change to a huge repository is usually fast.

Git also keeps a local bookkeeping copy of where the remote’s branches point, called a remote-tracking branch, visible as something like refs/remotes/origin/main. You never commit directly onto a remote-tracking branch; Git updates it automatically whenever you fetch or push, and it exists purely so commands like git status can tell you whether you are ahead of, behind, or diverged from GitHub without touching the network.

HTTPS vs. SSH

GitHub repositories can be reached over two different URL schemes. An HTTPS URL looks like https://github.com/yourusername/recipe-app.git and authenticates with a username and a Personal Access Token (GitHub removed plain password authentication over HTTPS in August 2021). An SSH URL looks like git@github.com:yourusername/recipe-app.git and authenticates with an SSH key pair you register with your GitHub account once. HTTPS works everywhere without extra setup and is the easiest starting point; SSH is more convenient long-term because, once your key is set up, you are never prompted for credentials again. Both point at the exact same repository — you can even switch a remote from one to the other without losing any history, which is shown in Example 2 below.

Syntax

Connecting a repository is managed with the git remote family of subcommands, plus git push to actually send commits:

git remote add <name> <url>
git remote -v
git remote set-url <name> <new-url>
git remote rename <old-name> <new-name>
git remote remove <name>
git push -u <remote> <branch>
Command What it does
git remote add <name> <url> Registers a new remote under a short name, usually origin, pointing at the given URL.
git remote -v Lists every configured remote and its fetch/push URLs. Add -v for "verbose"; without it you only see the names.
git remote set-url <name> <new-url> Changes the URL an existing remote points to, e.g. switching from HTTPS to SSH.
git remote rename <old> <new> Renames a remote without touching its URL or history.
git remote remove <name> Deletes a remote’s configuration entirely. This does not delete the repository on GitHub, only the local link to it.
git push -u <remote> <branch> Pushes the given branch to the remote and records it as the branch’s upstream, so future plain git push/git pull need no arguments. -u is short for --set-upstream.

Examples

Example 1: Connecting a fresh local repo to a new, empty GitHub repository

Create the GitHub repository first, through the web UI at github.com (click New repository) and leave it completely empty — no README, license, or .gitignore. GitHub will show you the remote URL on the next page. Then, locally:

git init -b main
git add .
git commit -m "feat: initial commit of recipe-app"
git remote add origin https://github.com/yourusername/recipe-app.git
git push -u origin main

Output:

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 330 bytes | 330.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/yourusername/recipe-app.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

git init -b main creates the repository with main as the initial branch name directly, avoiding the older default of master. git remote add origin <url> writes an [remote "origin"] section into .git/config; nothing is sent over the network yet. git push -u origin main is what actually uploads the objects, creates the main branch on GitHub, and links your local main to origin/main so that later you can simply type git push or git pull.

Example 2: Switching an existing remote from HTTPS to SSH

Suppose you connected over HTTPS but are tired of entering a token, and you have already added an SSH key to your GitHub account under Settings → SSH and GPG keys.

git remote -v
git remote set-url origin git@github.com:yourusername/recipe-app.git
git remote -v

Output:

origin  https://github.com/yourusername/recipe-app.git (fetch)
origin  https://github.com/yourusername/recipe-app.git (push)
origin  git@github.com:yourusername/recipe-app.git (fetch)
origin  git@github.com:yourusername/recipe-app.git (push)

The remote is still named origin and still points at the exact same repository on GitHub — only the transport and the credential mechanism changed. No history, branches, or commits are affected; this edits one line in .git/config.

Example 3: Creating and connecting the GitHub repo in one step with the gh CLI

If you have the official GitHub CLI (gh) installed and authenticated, you can skip the website entirely and create the remote repository from your terminal, in the same command that adds the remote and pushes:

gh repo create recipe-app --private --source=. --remote=origin --push

Output:

✓ Created repository yourusername/recipe-app on GitHub
✓ Added remote origin
✓ Pushed commits to https://github.com/yourusername/recipe-app.git

--source=. tells gh to use the current directory as the repository content, --remote=origin adds the remote for you (equivalent to git remote add), and --push performs the initial push automatically. This is a convenience wrapper; internally it still runs the same git remote add and git push operations shown in Example 1.

How It Works Step by Step

When you run git push -u origin main against a freshly connected remote, Git performs several distinct steps:

  1. Git reads the URL for origin out of .git/config and opens a connection to GitHub over HTTPS or SSH.
  2. Git asks the remote which refs (branches and tags) it currently has, so it knows what is missing.
  3. Starting from your local main commit, Git walks backward through parent commits, trees, and blobs, collecting every object the remote does not already have.
  4. Those objects are compressed into a single packfile and uploaded to the remote’s receive-pack process.
  5. The remote unpacks the objects into its own object database and moves its refs/heads/main pointer to your new commit — this is the moment the push actually "succeeds" or is rejected.
  6. Locally, Git updates the remote-tracking ref refs/remotes/origin/main to match, and because you used -u, it also writes branch.main.remote = origin and branch.main.merge = refs/heads/main into .git/config, establishing the upstream relationship.

Common Mistakes

Mistake 1: Initializing the GitHub repo with a README, then pushing

If you tick "Add a README file" while creating the GitHub repository, it already has a commit that your local repository has never seen. Pushing then fails:

git push -u origin main

Output:

To https://github.com/yourusername/recipe-app.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/yourusername/recipe-app.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.

Git refuses to overwrite commits it hasn’t seen. The fix is to bring the remote’s commit into your history first, then push:

git pull --rebase origin main
git push -u origin main

The simplest long-term fix is to just leave the GitHub repository completely empty when you plan to push an existing local project into it, as in Example 1.

Mistake 2: Adding a remote that already exists

Running git remote add origin <url> a second time fails, because the name origin is already taken:

fatal: remote origin already exists.

Use set-url to change an existing remote instead of trying to add it again:

git remote set-url origin https://github.com/yourusername/recipe-app.git

Mistake 3: Entering a GitHub password over HTTPS

GitHub no longer accepts account passwords for Git operations over HTTPS:

remote: Support for password authentication was removed on August 13, 2021.
fatal: Authentication failed for 'https://github.com/yourusername/recipe-app.git/'

Use a Personal Access Token in place of the password when prompted (generate one under Settings → Developer settings → Personal access tokens), or avoid the prompt entirely by switching the remote to SSH as shown in Example 2.

Best Practices

  • Leave a new GitHub repository completely empty when you intend to push an existing local project into it, to avoid an immediate history conflict.
  • Always run git remote -v after adding or editing a remote to confirm it points at the URL you expect before pushing.
  • Prefer SSH for a repository you’ll push to often; it avoids repeatedly entering a token and is easy to set up once per machine.
  • Use -u on your very first push to a new branch so later pushes and pulls don’t require typing the remote and branch name.
  • Never paste a real token or password into a command example, script, or commit — treat a leaked token like a leaked password and revoke it immediately in GitHub settings.
  • Keep the remote named origin unless you have a specific reason not to (for example, when also tracking a separate upstream remote for an open-source fork) — it’s the convention every Git tool expects.

Practice Exercises

  • Create a new empty repository on GitHub named practice-remote. Locally, initialize a project with git init -b main, make one commit, connect it with git remote add, and push it with -u so that a plain git push works afterward.
  • Starting from the repository you just connected, use git remote set-url to switch it from HTTPS to SSH (or vice versa), then confirm the change with git remote -v.
  • Deliberately create a GitHub repo with a README enabled, try to push an unrelated local commit into it, observe the rejection, and resolve it with git pull --rebase before pushing again.

Summary

  • A remote is just a name-to-URL mapping stored in .git/config; Git itself has no built-in knowledge of GitHub.
  • git remote add origin <url> registers the connection; it does not transfer any data by itself.
  • git push -u origin main uploads the missing commit, tree, and blob objects as a packfile, moves the remote’s branch pointer, and sets up the local branch’s upstream tracking.
  • HTTPS remotes authenticate with a Personal Access Token (never a password); SSH remotes authenticate with a registered key pair.
  • git remote -v, set-url, rename, and remove let you inspect and fix a remote without losing any repository history.
  • A push is rejected when the remote has commits your local branch hasn’t fetched yet — fetch or pull first rather than forcing it.