You might also like
TL;DR
Most guides tell you to hack your .ssh/config to manage multiple Git
identities and SSH keys — we strongly advise against this. This guide
introduces other superior identity management methods, focusing on two scalable
options via core.sshCommand and a powerful, niche shell script for dynamic SSH
key switching.
Introduction
Don’t be like everyone else, don’t change your .ssh/config because you have
multiple accounts on a forge. Be better.
You have probably seen this method on all the guides on the internet. Screw
those guides. I’ll show you how to manage multiple identities with git.
We’ll start with my least recommended, but obligatory option:
A. Change the remote URI via ssh_config
And by ssh_config, I mean $HOME/.ssh/config.
The idea behind this is that by using .ssh/config, you can create aliases for
a GitForge and configure a specific identity for each alias. In this case, you
will use clientforge as your client’s host and the original name gitforge.com
for your projects. Your .ssh/config will look a little something like this:
In your project’s repository, you’ll need to change the remote of your liking with the set-url command for remotes:
The downside to this approach is that you need to remember which host to use for each project, and you can no longer copy/paste specific commands from the forges after you’ve created a new repository. The upside is that it doesn’t require any additional git configuration changes.
Quick gitconfig recap
Before we head into the other options, it is essential to know that git stores
its configuration in several locations. You have a system-wide configuration in
/etc/git/config, a global one in $HOME/.gitconfig, and a local one in your
repository $GIT_DIR/.git/config. This allows you to hone your configuration
from global to very specific at the repository level. There are also several
include directives, such as the
includeIf
B. Using different sshCommands (or options) for a project
As said, we have a per-repository config. For a single project, you can set a
custom
core.sshCommand
core.sshCommand is used to replace the full ssh-command. This allows you
to use the key associated with your work account. You can do this by running
the following command:
This should result in a line similar to this in $GIT_DIR/.git/config:
The identityOnly=yes setting is only there to prevent SSH from looping over
all your SSH keys and potentially using a different SSH key. The -F /dev/null
disables using your .ssh/config. You could also use a different SSH config
for just the sshCommand, e.g., sshCommand = -F ~/.ssh/config-client, and
set the correct SSH options in that file. You could also use the entire command
here, e.g., ssh -i ~/.ssh/id_client -o IdentityOnly=yes -F /dev/null.
This option only scales well when you have only one or two projects, which leads me to the next option.
C: Using different sshCommands (or options) per project directories
You can use different git configurations depending on the project directory you
are in. For this, we use the previously mentioned [includeIf] directive,
which allows you to include a separate configuration based on the path of your
projects. So all projects in $HOME/work/client share the same git
configuration. In your $HOME/.gitconfig, you need to add the following
snippet:
Now in $HOME/.config/git/client.config, you can configure the
core.sshCommand as done in the previous example. You can also use this for
configuring other bits of git for your project(s), such as a different e-mail
address, name, etc. This way, you can manage multiple projects with ease using
a single configuration file.
Custom SSH wrapper script: Dynamic identity switching per remote or username/group
The most niche and flexible of all, and can work as a combination of the last
two options. The problem I tried to solve while developing this solution is
that I had two accounts on the same GitForge, and I have a ton of repositories.
Because the hostnames of the repositories are placed in the
myrepos
.ssh/config. And I also didn’t want to change the
host for all my personal projects. In the past, I could use my account to
commit to the company’s repos. However, after they underwent ISO certification,
we had to log in with SSO, and only those accounts were authorized to make
changes. So I had to create two separate accounts with two distinct SSH keys.
As I was already using the includeIf directives for various directories, I
wanted to be able to select a different SSH key for specific remotes. Now there
is an includeIf directive that supports including a configuration when a
remote has a specific endpoint:
This approach will not work as expected because it includes the configuration,
regardless of where you push your changes. So we will use an SSH-wrapper script
and configure it as the core.sshCommand command.
First, you need to know how git sends its SSH command. You can test this by
making a script and printing the things to STDERR or using set -x. Printing
to STDOUT from within the script will issue warnings:
You can also use GIT_TRACE=1 while issuing a command that triggers something
on your remote (git fetch, git pull, git push, etc). Git has several
options on how and what it sends to SSH. You can change these options by
setting the
ssh.variant
simple, changed it to ssh, and now it is
back to the default auto.
A small script like this will do the job for testing purposes:
And you issue the command:
After which you run git fetch:
As you can see, the last bit of the parameters is what we are interested in. It has the repository, and we can select the correct ssh-key based on that. We can do that in two ways:
- We define the sshIdentityFile on the remote itself, which is hostname and
repo gnostic:
git@gitlab.com:waterkip/repo.git - We define the sshIdentityFile more global and it depends on just the owner
of the repo:
waterkip
Or we do it solely based on the username:
The wrapper script:
This allows me to use different accounts for each remote within the same repository. And adding a second account becomes dead easy:
You can find a to date version of the ssh wrapper script in my bum toolkit source code. This version also supports sshOpts on remote and global level.
You can grab it from
git port
I’ve also supplied a patch in 2026 to upstream to have this logic included in
git. Git seems to think the ssh_config options are suffice, which I totally
disagree with. And for those who agree with that disagreement, you can use my
ssh-wrapper_port
next branch of git.
Conclusion
These are the four ways to configure multiple identities and SSH keys for git. Most developers will want to choose either option B or C, depending on the number of repositories they maintain. A few, like myself, will use option D for specific repositories where path based logic cannot be applied. All methods except for option A have been in use by me for at least three years. Pick B, C, or D but never A. The choice ultimately depends on how you organised your repositories.
A note on an ingenious alternative
I do want to point out one particular use case for
Ingo Richter’s post
Comparison table
| Method | Description | Pros | Cons |
|---|---|---|---|
A. SSH Config Alias (.ssh/config) |
Use host aliases and identity files in ~/.ssh/config to switch identities per-host |
Simple to set up; no Git config changes needed | Must remember aliases; doesn’t match hostnames directly |
B. Per-Repo core.sshCommand |
Set a specific SSH command in a repos local Git config | Works per-repo; avoids global conflicts | Doesn’t scale well for many repos |
C. includeIf for Path-Based Config |
Use Git’s includeIf to apply config (e.g. sshCommand, name/email) based on repo path |
Scales well; central config for client/work projects | Requires project folder structure discipline |
| D. Custom SSH Wrapper Script | Use a script as core.sshCommand to dynamically pick SSH key based on remote repo or group/owner |
Highly flexible; works even with multiple remotes in same repo | Requires initial scripting (unless you use the provided zsh script) |
| E. Custom git fork | Use a custom build git to have the same functionality as D | Same as D | Requires custom builds on every machine you have |