Configuring SSH Keys for Multiple GitHub Accounts

Managing different SSH Keys for different Hosts is well-understood. Different keys for the same host (e.g., github.com), based on which GitHub Organization we’re working with, that’s… a bit trickier. But, we have the technology!

We’re going to configure our SSH and Git clients to seamlessly switch SSH keys between our personal GitHub Account and our GitHub Enterprise Cloud, EMU Account.

Futurama character Fry and a soldier standing outside a 'War Room' door. A screen on the door has the words 'YOU NEED A BLUE KEY'. The soldier is holding up a comically large blue key.
You need the right key.

🔐 Typical Multi-Key Setup

Most guides and information we’ll find on the Internet about managing multiple SSH keys focus on multiple Host values. As in, use key A for example.com and key B for stevenharman.net. A simplified ~/.ssh/config for that might look something like:

Host example.com
  IdentityFile ~/.ssh/id_a_ed25519

Host stevenharman.net
  IdentityFile ~/.ssh/id_b_ed25519

🤹 Different HostNames, Different Keys

But what happens when the HostName, i.e., the real host name that will be used for the SSH connection, for both is the same? For example, when we have a personal GitHub Account (for Open Source work, some private repositories, and maybe even some repos belonging to other GitHub Organizations), and second GitHub Enterprise Cloud Account provisioned by our employer.

For brevity, let’s say our personal GitHub Account is fry. And our work for Planet Express, Inc., has a GitHub Enterprise Cloud instance, and provisioned us a fry_plnx Enterprise Managed Users Account.

In both cases, the SSH HostName is github.com. We might try a configuration like this:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_ed25519

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_plnx_ed25519

This configuration will work for git talking to GitHub for the fry Account, because it is matched first. But when working with a repository for the fry_plnx Account, the same first Host github.com entry matches, and uses the id_fry_ed25519 SSH Key. But the fry_plnx Account doesn’t know about that key, and so… we see errors.

$ git clone git@github.com:planet-express/delivery_service.git
  ERROR: Repository not found.
  fatal: Could not read from remote repository.
  
  Please make sure you have the correct access rights
  and the repository exists.

🤡 Use Different Host values

At this point, most articles on the Internet will tell us to override the SSH command used to connect, or (IMO, slightly better) use different Host values for each. The later would look something like this:

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_ed25519

Host github-plnx
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_plnx_ed25519

A big downside to this technique is that any time we’re cloning a new repository for our fry_plnx Account, we’ll need to adjust the URL used. Meaning we can no longer copy/pasta the command from the GitHub UI. Nor rely on muscle memory we’ve built up over years of using our personal Account.

# Instead of the actual URL
$ git clone git@github.com:planet-express/delivery_service.git

# Substitue in our custom Host value for the `github.com` part
$ git clone git@github-plnx:planet-express/delivery_service.git

And this will work! But we have to remember to manually substitute those values for each repository we clone. It’s toil. And I detest toil.

🤖 Automate Substituting the Host

I happened across an article using Git’s url.<base>.insteadOf variable to swap URLs. We can use that to teach Git about our Host github-plnx, and have it auto-magically swap github.com back in when talking over SSH to GitHub. We’ll add this to our ~/.gitconfig:

# See custom `Host github-plnx` in ~/.ssh/config
[url "github-plnx:planet-express"]
  insteadOf = git@github.com:planet-express

By including the planet-express GitHub Organization name in the match for the url variable, Git will alter the URL git@github.com:planet-express/delivery_service.git by matching github.com:planet-express and swapping in github-plnx:planet-express. Then the SSH config matches the host github-plnx, and using that matched config, replaces github-plnx with the actual user (git) and host name (github.com). At which point we’re back to the original URL. 🎉 NEAT!

With that in place, Git commands for repositories under github.com/planet-express will use the id_fry_plnx_ed25519 SSH key. Any other repositories will fail to match the github-plnx host, thus using the regular Host github.com SSH Config, and the id_fry_ed25519 key.

NOTE: We’d need to add a similar entry for other Organizations in the GitHub Enterprise Cloud instance that we need to work with.

🧹 Keeping .gitconfig Clean

If you’ve got your dotfiles in Git, you might want to keep employer and/or machine-specific configuration out of your ~/.gitconfig. For example, our work machine needs to know about the custom url "github-plnx:planet-express" Git config, but our personal MacBook likely doesn’t! Or maybe we don’t, or cannot, expose every GitHub Enterprise Cloud Organizations we’re a part of.

Luckily, we can leverage Git’s ability to include other config files to keep such customizations out of Git. We can adjust our ~/.gitconfig, which is in our dotfiles Git repo, to include another config file which is NOT in Git. Add the following the bottom of our ~/.gitconfig:

[include]
  path = ~/.gitconfig_custom

If this file isn’t present, Git just ignores it. As is the case on my personal MacBook. 😅 Phew!

When it is present, Git will load the file, allowing us to override or add additional configuration. Now we move our [url …] configuration from ~/.gitconfig into the new ~/.gitconfig_custom file:

[url "github-plnx:planet-express"]
  insteadOf = git@github.com:planet-express

And voila! We are switching SSH keys automatically, and our ~/.gitconfig is clean of any knowledge about the specific GitHub Organizations, etc…