Skip to content
webkida wiki
Go back

How GitHub SSH authentication works, and multiple accounts

Updated:

Explains how GitHub SSH authentication picks a key, then connects that to a second-account key, a Host alias, and includeIf

When you push to GitHub with Git, SSH picks one key behind the scenes and authenticates with it. Day to day, one key is enough, so you rarely think about which key is in use.

When you need another account, you have to present a different key to the same github.com. If you handle that loosely, you can land on the wrong account, or the name recorded on commits can drift from the account you authenticated as.

First we’ll look at how SSH chooses a key, then connect that to a Host alias for the other account and to includeIf, which switches the commit author by folder.

Terms

SSH key

A private-key and public-key pair. The private key lives on the Mac; the public key lives on GitHub. The Mac presents the public key, GitHub confirms it is registered, asks for a signature on a challenge, verifies that signature with the public key, and that is how it decides which account you are.

Host alias

A connection name you write in ~/.ssh/config for your own use. It is not a real server name. Combined with HostName github.com, it means “when I connect under this name, use this key.” The name for the other account is github-other.

IdentityFile

The path of the private key SSH will present. Candidates come, in order, from IdentityFile in ~/.ssh/config, keys loaded in ssh-agent, and default names such as ~/.ssh/id_ed25519.

includeIf

A Git mechanism that loads config when a local directory condition matches. What you put in gitdir: is a folder on your PC, not a path on GitHub. Here it is used to switch the commit author (user.name / user.email).

Commit author

The user.name and user.email recorded on a commit. That is independent of SSH authentication (which GitHub account you push as). Even if you push with the other account’s key, the author stays your usual name if Git config is still the usual one.

How SSH authentication works

On the Mac you have a private key and a public key.

~/.ssh/id_ed25519      ← private key
~/.ssh/id_ed25519.pub  ← public key

Once you register the public key on the GitHub account you normally use, GitHub ties that key to that account. There is no direct definition on the Mac that says “id_ed25519 = this account.”

Mac → presents the public key → GitHub confirms registration → sign the challenge → the account is decided

[On connect] Mac → GitHub

Mac: “I’ll present a public key first. I’ll offer candidate keys in order.”

[On connect] GitHub

GitHub: “That public key is registered. Sign this. This key belongs to the usual account. Hi username!”

The Mac is not sending an account name. It presents public keys, and only the key that is accepted is used to sign with the private key. GitHub checks each presented public key one at a time and, if it is registered, verifies the signature. It is not the account side walking through its registered keys in order.

Who tries keys in order?

It can feel like “after the signature is sent to GitHub, GitHub walks through registered public keys in order.” The side that mainly tries keys in order is the SSH client on the Mac.

SideWhat it does
Mac (SSH client)Presents candidate keys in order: config → ssh-agent → id_ed25519, and so on
GitHubFor the one public key that was presented, checks “is it registered?”, and if so runs challenge → signature verification

GitHub does not “walk every key registered on other accounts.” For the one public key the Mac handed over, it looks it up in the DB and scores a hit. GitHub does not know up front whether you want the usual account or the other account. Which public key was presented is all that decides the account.

If the first key is unregistered, GitHub rejects it and the Mac presents the next key. Authentication ends at the moment one is accepted.

Mac:  presents public key A (Offering public key)
GitHub: A is unregistered → reject
Mac:  presents public key B
GitHub: B is registered → return a challenge
Mac:  signs with private key B
GitHub: verifies with public key B → OK → Hi username!

The private key itself is never sent. The public key goes out first, and only the accepted key does the signature exchange.

To confirm:

ssh -T git@github.com

On success you see the username GitHub recognized, like Hi USERNAME! You've successfully authenticated....

Why id_ed25519 is used

SSH does not brute-force every file under ~/.ssh. Key candidates mainly come from the following.

  1. IdentityFile in ~/.ssh/config
  2. Keys loaded in ssh-agent
  3. Standard default key names such as ~/.ssh/id_ed25519 and ~/.ssh/id_rsa

id_ed25519 is not a GitHub-only name; it is a standard default key name for SSH. You may already have the same key registered on other services, such as GitLab or a server you run yourself.

To see which keys are candidates:

ssh -G github.com | grep -i identityfile

Which keys were actually offered, and which one was accepted, you can see in a verbose log.

ssh -vT git@github.com

To pull out only the key-related lines:

ssh -vT git@github.com 2>&1 | grep -E "Offering public key|Server accepts key"

Offering public key is a candidate that was presented. Server accepts key is the key GitHub accepted.

sequenceDiagram
    participant SSH as SSH client
    participant GH as GitHub

    SSH->>SSH: ① Gather key candidates<br/>config / agent / default names
    SSH->>GH: ② Offering public key
    GH->>GH: ③ Match the public key
    GH-->>SSH: ④ Server accepts key
    Note over GH: ⑤ The account is decided

Figure: SSH presents candidate keys, and GitHub’s public-key match decides the account.

SSH setup for multiple accounts

The approach is leave the usual account as the default, and add an exception only for the other account. Keep the existing id_ed25519 as-is.

Create a key for the other account

ssh-keygen -t ed25519 -C "OTHER_ACCOUNT_EMAIL" -f ~/.ssh/id_ed25519_github_other

What you get:

~/.ssh/id_ed25519_github_other      ← private key
~/.ssh/id_ed25519_github_other.pub  ← public key

Copy the public key on macOS.

pbcopy < ~/.ssh/id_ed25519_github_other.pub

On the other GitHub account, paste it under Settings → SSH and GPG keys.

Add a Host alias in ~/.ssh/config

You do not have to spell out the usual-account side. Add only the other account.

Host github-other
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_github_other
    IdentitiesOnly yes

github-other is not a real server; it is a Host alias you chose. The actual destination is HostName github.com.

The git in User git is not a GitHub account name. For SSH to GitHub the SSH username is fixed as git, and whether you are the usual account or the other account is decided by the key you present.

IdentitiesOnly yes tells SSH not to try other keys loaded in the agent first. That avoids the default key succeeding first when you connect under the alias.

What happens when you authenticate as github-other

For ssh -T git@github-other or git push (when the remote is git@github-other:...), the Mac reads ~/.ssh/config and takes only the github-other key as a candidate. Because IdentitiesOnly yes is set, it does not offer the usual key from ssh-agent first.

[When running ssh -T git@github-other] Mac

I’ll read config. Host is github-other, the key to use is only id_ed25519_github_other. IdentitiesOnly yes, so I won’t offer agent keys.

[Same moment] Mac → GitHub

Presenting public key id_ed25519_github_other (Offering public key)

[Same moment] GitHub

That public key is registered on the other account. Here’s a challenge — sign it.

[Same moment] Mac → GitHub

Signed with private key id_ed25519_github_other

[Same moment] GitHub

Signature OK. Hi other-user! Authenticated as the other account.

git push is the same. If the remote is git@github-other:OTHER_ACCOUNT/my-repo.git, every push connects via github-other over SSH, and only the same key is used.

git remote -v
# origin  git@github-other:OTHER_ACCOUNT/my-repo.git (push)

git push
# internally: SSH to git@github-other → authenticate with id_ed25519_github_other

An example of checking with a verbose log:

ssh -vT git@github-other 2>&1 | grep -E "Offering public key|Server accepts key|Authenticated to"

The output looks like this (the fingerprint differs per environment).

debug1: Offering public key ED25519 SHA256:xxxxxxxx ... id_ed25519_github_other
debug1: Server accepts key: ... id_ed25519_github_other
Authenticated to github.com ([140.82.121.4]:22) using "publickey".
Hi other-user! You've successfully authenticated, but GitHub does not provide shell access.

There is only one Offering public key line because IdentitiesOnly yes narrowed the candidates to one key. The usual id_ed25519 does not appear.

On the other hand, if IdentitiesOnly yes is missing and the usual key is loaded in the agent, even a Host meant for the other account can offer id_ed25519 first and authenticate as the usual account.

debug1: Offering public key ED25519 SHA256:aaaaaaaa ... id_ed25519
debug1: Server accepts key: ... id_ed25519
Hi default-user! ...

When the symptom is “this is the other account’s repository, but I have no push permission,” a quick check is whether the remote Host is github-other and whether IdentitiesOnly yes is set.

To confirm authentication:

# usual account
ssh -T git@github.com

# other account
ssh -T git@github-other

The end mapping looks like this.

github.com     → ~/.ssh/id_ed25519              → usual account
github-other   → ~/.ssh/id_ed25519_github_other → other account

[When connecting as the other account] Mac → GitHub

Mac: “github-other, so I’ll present only id_ed25519_github_other.”

[When connecting as the other account] GitHub

GitHub: “That public key is tied to the other account. Signature OK. Hi other-user!”

Clone a repository for the other account

The usual SSH URL GitHub shows is this.

git@github.com:OTHER_ACCOUNT/repo.git

To authenticate as the other account, change the Host part to the alias.

git clone git@github-other:OTHER_ACCOUNT/repo.git

Here OTHER_ACCOUNT is the repository owner. It is not a specifier for the authenticating user. The authenticating account is decided by the SSH key.

After you clone, that URL is stored as remote origin.

git remote -v
origin  git@github-other:OTHER_ACCOUNT/repo.git (fetch)
origin  git@github-other:OTHER_ACCOUNT/repo.git (push)

From then on, ordinary git fetch / git pull / git push will use the other account’s key.

To switch the remote on an existing repository:

git remote set-url origin git@github-other:OTHER_ACCOUNT/repo.git

The same applies to private repositories. If you can clone, at least SSH authentication and read permission for the other account have succeeded. If you do not have write permission, clone can succeed and push will still fail.

HTTPS is a different path

git clone https://github.com/OTHER_ACCOUNT/repo.git

also works, but that is HTTPS authentication, not SSH.

OTHER_ACCOUNT in the URL is the repo owner. It is not a specifier that authenticates you as the other account. For a private repository, HTTPS-side credentials are used — a Personal Access Token, Git Credential Manager, macOS Keychain, and so on.

So the SSH setup so far:

github.com     → usual account
github-other   → other account

does not apply to https://github.com/.... If you clone via HTTPS from GitHub’s green button, the ~/.ssh/config branch never runs.

Put together, there are two authentication paths.

Authentication to GitHub
├─ SSH   → SSH key (github.com / github-other)
└─ HTTPS → credential / token

Commit author
└─ Git config → user.name / user.email

Switch the commit author by folder

Even if you clone with the other-account URL, the commit author does not switch automatically. If global user.name / user.email are the usual account, you get this mismatch.

Push authentication to GitHub → other account
Commit author                 → usual account

To check:

git config user.name
git config user.email

In a local folder for the other account, switch the Git author there as well.

As an example, put other-account repositories under ~/work/github-other/.

~/work/
├── repo-default/
└── github-other/
    ├── repo-1/
    ├── repo-2/
    └── nested/repo-3/

Other-account settings in ~/.gitconfig-other:

[user]
    name = OTHER_NAME
    email = OTHER_EMAIL

Main ~/.gitconfig:

[user]
    name = DEFAULT_NAME
    email = DEFAULT_EMAIL

[includeIf "gitdir:~/work/github-other/"]
    path = ~/.gitconfig-other

That splits things like this.

~/work/repo-default/           → usual-account author
~/work/github-other/repo-1/    → other-account author
~/work/github-other/repo-2/    → other-account author
~/work/github-other/nested/... → other-account author

gitdir:~/work/github-other/ is a local path on your PC, not a path on GitHub. If you specify the parent folder, it applies to Git repositories under it.

You can see which file a value was read from with:

git config user.name
git config user.email
git config --show-origin --get user.email

includeIf does not switch SSH authentication

For example, the following keeps the folder as other-account, but the SSH Host stays github.com.

cd ~/work/github-other
git clone git@github.com:OTHER_ACCOUNT/repo.git

The usual account’s key is used. To clone as the other account, you still need the Host alias as before.

git clone git@github-other:OTHER_ACCOUNT/repo.git

The roles are completely separate.

~/.ssh/config
→ decides the SSH authentication account for GitHub
→ which public key / private key to use

Git config / includeIf
→ decides the user.name / user.email recorded on a commit
→ the repository's commit author

Typical work on the other account looks like this.

cd ~/work/github-other
git clone git@github-other:OTHER_ACCOUNT/repo.git
cd repo

git config user.name
git config user.email
git remote -v

The state you want:

user.name / user.email → other account
origin                 → git@github-other:OTHER_ACCOUNT/repo.git

From then on, ordinary git commit / git push makes both the commit author and GitHub authentication the other account.

sequenceDiagram
    participant SSH as SSH
    participant GH as GitHub
    participant Git as Git

    rect rgba(120, 140, 180, 0.1)
        Note over SSH,GH: SSH authentication
        SSH->>GH: ① github.com uses id_ed25519
        GH-->>SSH: ② Usual account
        SSH->>GH: ③ github-other uses the other key
        GH-->>SSH: ④ Other account
    end

    rect rgba(120, 160, 130, 0.1)
        Note over Git: Commit author
        Git->>Git: ⑤ global is the usual author
        Git->>Git: ⑥ includeIf gitdir is the other author
    end

Figure: The SSH Host and Git includeIf are separate layers. Only when both are in place do authentication and author become the other account.

Takeaways


Share this post:

Previous Post
【Search Console】Sitemap and indexing traps on first setup
Next Post
[Cursor] Subagents and background execution