How to set up GIT and OpenSSH for multiple identities, also with PaaS like GitHub/Gitlab etc.
If you use your computer in multiple contexts (private, work, multiple client projects), you might run need to use different identities for these contexts. For
git this usually boils down to
- different SSH keys for remote pull/push
- different names and e-mail-addresses for the git commit log
This can be a pain to work with – after trying multiple solutions I found on the web (or puzzle pieces thereof), I created a combination that works completely automatic once it’s set up, and that I think might be worth sharing.
Detailed problem description
The issue is actually two-fold:
- For every pull/push, ssh needs to determine, which SSH key to use. The default behaviour of OpenSSH (to just try the same key (or list of those) for every connection) is problematic with
git, since a key can be bound to one SSH user only. If you have multiple accounts on GitHub you need to use different keys for each account.
- For every commit, git needs a user identity (real name, e-mail) which is then persisted forever in the git log. While your name might be identical all the time, using your private address in work repositories or vice versa might be not desirable.
Using different ssh keys depending on context (especially with multiple PaaS Accounts)
GitHub uses a single host name
github.com for all repositories, and when using GitHub via SSH, the SSH key identifies the user. The well-known OpenSSH feature of key selection based on host names does not help, since there is only one host:
github.com. I guess, (also) because of this problem, GitHub recommends using only one account per user – which works until there is this new customer, which requires you to create an extra GitHub account to join the customer’s organization, or uses GitHub enterprise managed users.
The same might apply for other git PaaS platforms using a single host name.
Use different git author name and e-mail depending on context
Git uses your user name and e-mail in every commit you make, and persists it to the log. To keep things clean, you may not want to use your work email when committing to your private pet projects, or the other way around: use this embarrasing
email@example.com e-mail-address when committing at a project for your employer or for some important, but stiff client.
Disclaimer: This solution is a combination of approaches found elsewhere, and tested to be working on MacOS. It should work similarly on other Linux/Un*xes and Windows Subsystem for Linux, not so sure about other Windows setups (input welcome). Please excuse me for not giving credit to all stackoverflow threads, blog posts and gists that I found along the way. I did not invent most of this, I just combined it.
This solution uses directories as primary context containers, automatically switching identities based on the current working directory. So, for each context, you create one directory, and that directory then hosts subdirectories for all software projects (git repositories) of that context. Switching contexts is done by just using
Typically, unixoid systems have their user homes in
/home. On MacOS, all user homes are in
/Users. Since I am using MacOS, this is what this example uses. Also, while this example is using only GitHub, it should work for other providers (e.g. Gitlab) as well.
This example creates two contexts – one for my private stuff, and one for my work environment. You can have as many contexts as you like – just do not let these directories overlap – do not use context folders that are nested in other context folders. Instead keep them separate, like this:
/Users/easimon/development/ \_ hobby/ \_ my_blog/ \_ hobby_project_2/ \_ work/ \_ company_website/ \_ some_microservice/
/Users/easimon/development/hobby /Users/easimon/development/hobby/my_blog /Users/easimon/development/hobby/hobby_project_2 /Users/easimon/development/work /Users/easimon/development/work/company_website /Users/easimon/development/work/some_microservice
Effectively you have two “context root” folders. Whenever you
cd to one of the directories, you assume a specific ssh and git identity. All git clones are can then be subdirectories of one of these context roots.
The implementation of this is two-fold: Configure OpenSSH to automatically select the correct SSH key, and configure git to automatically configure real name and e-mail address.
Match clause you can create working-directory specific settings in OpenSSH. If necessary, you can reconfigure all options based on current working directory, but we’ll just switch the
ssh_config allows for directory-specific configuration using the following
Match host github.com exec "pwd | grep -qE '^/some/absolute/path(/.*)?$'" IdentityFile ~/.ssh/some-key
Step by step
You require one ssh key per context, which must / should do not be one of the “default”
id_dsa etc. keys, since there’s some OpenSSH automagic to always include these keys when trying to authenticate. Also, don’t add these “default” keys to your GitHub account ssh keys, as doing so can interfere with this setup. I also recommend to not set a default catch-all-key for github.com globally. Instead, keep all your git ssh clones inside one of the context root directories you defined.
Create one SSH identity (key file) for each context:
ssh-keygen -f ~/.ssh/id_hobby ssh-keygen -f ~/.ssh/id_work
Then, to enable automatic identity switching, add the following to
# Global options Host * # Always only use specified identities, do not fall back to # everything found in ssh-agent. IdentitiesOnly yes # Repeat following for each context ## When in "hobby" context directory, use "hobby" ssh key for github.com Match host github.com exec "pwd | grep -qE '^%d/development/hobby(/.*)?$'" IdentityFile ~/.ssh/id_hobby ## When in "work" context directory, use "work" ssh key for github.com Match host github.com exec "pwd | grep -qE '^%d/development/work(/.*)?$'" IdentityFile ~/.ssh/id_work ### Of course, other hosts that require this context ### can be added as well, just using the host name Host bitbucket.work.com User git IdentityFile ~/.ssh/id_work
For each directory, make sure you get the grep expression right, for a subfolder
foo/barin your home directory the expression is
How it works
The magic is the
Match keyword, followed by two match criteria.
host github.com: match only when connecting to github
exec "pwd | grep -qE '^%d/development/work(/.*)?$'"match only when the current working directory is in
Explanation for the syntax of the grep expression:
%dis expanded by ssh, and means “local user’s home directory”
^...$makes the expression exhaustive, so it only matches when you’re actually in that directory, and not in some other directory that contains this path as a substring
(/.*)is to make sure it matches only in this folder (and subfolders), but not e.g. in a folder named
Testing if SSH key selection is working
After doing so, try the following:
$ cd ~/development $ ssh firstname.lastname@example.org email@example.com: Permission denied (publickey). $ cd ~/development/hobby $ ssh firstname.lastname@example.org PTY allocation request failed on channel 0 Hi easimon! You've successfully authenticated, but GitHub does not provide shell access. Connection to github.com closed. $ cd ~/development/work $ ssh email@example.com PTY allocation request failed on channel 0 Hi easimon-work! You've successfully authenticated, but GitHub does not provide shell access. Connection to github.com closed.
When outside any context directory, the ssh connection should fail. If it succeeds, one of your default ssh keys (e.g.
~/.ssh/id_rsa) is accepted by GitHub, and this spoils any attempt to switch the key to something else in “real” context folders.
For each context directory, the ssh connection should succeed, identify you by the expected GitHub handle and then drop the connection. Check if the
Hi your-handlematches your expectations.
Now, you’re all set for cloning all your repositories into subfolder of the corresponding context folder, e.g.
cd ~/development/work git clone firstname.lastname@example.org:some-org/some-repository.git cd some-repository
If it does not work: You can use
ssh -v to debug key selection, increase the number of
v to increase verbosity, e.g.
Git User Name and E-Mail configuration
Using a similar approach to the OpenSSH config, you can also switch git user name and e-mail based on current working directory. Like with OpenSSH, you can configure all other git options this way, but so far I found only username and e-mail useful options to alter.
Git allows directory-specific configuration using the following snippets:
[includeIf "gitdir:/some/path"] path = /some/path/to/another/gitconfig
And the file in the
path can then override git options for that directory.
Step by step
For each of the context folders, create a section like the following in
[includeIf "gitdir:~/development/hobby/"] path = ~/development/hobby/.gitconfig [includeIf "gitdir:~/development/work/"] path = ~/development/work/.gitconfig
This defines a gitconfig to include when inside a specific folder. The path must point to a valid git configuration file, but its location and name does not matter – I just name these
.gitconfig and put them into the context root, but that’s just personal preference.
And then for each
path create a file containing the following
[user] name = My Full Name email = email@example.com
Now, test this by committing something (not pushing) to an arbitrary repository in each context, and then watching
git log. The log should display the specified name and e-mail.
You’re now all set for automatic identity selection based on working directories.
While this setup is more complex and tricky than I’d prefer, once you have it running, I think it works like a charm.
Appendix: Alternatives and their downsides
SSH key selection using environment variables
You can set an environment variable like this
GIT_SSH_COMMAND='ssh -i ~/.ssh/id_work'
and make this toggle using direnv or similar. This introduces another new tool, so I skipped diving into this.
SSH key selection using .gitconfig
You can also set the SSH key using path-specific
.gitconfigs, like this:
[user] name = My Full Name email = firstname.lastname@example.org [core] sshCommand = ssh -i ~/.ssh/id_work
This actually works for most tools I encountered, and has been the solution of my choice a long time. But is not respected by e.g.
terraform when downloading modules, since terraform does not seem to use the
git cli to check things out, or at least completly ignores any
git cli configuration. So if your terraform modules live in a private repository,
terraform init in a root module refering to these private modules will not select the correct SSH key to check them out (it will also not have a correct git user name and e-mail, but that’s irrelevant for reading repositories).
ssh_config instead is a level lower – and more probable to be in effect for applications using SSH. At least for terraform, it works.
Create alternative hostnames for GitHub
You can create alternative hostnames for github.com, using
CNAMEs or ssh_config:
Host github.work.com User git Hostname github.com IdentityFile ~/.ssh/work
This solves the problem by creating a new one:
- When creating a DNS
CNAME, it might be comfortable for most users, but it requires to have a domain at hand to add this DNS
CNAMEentry. It’s also a bit sneaky to create
CNAMEs in your domain pointing to hosts not under your control.
- Using the hostname redefinition in
ssh_configdoes not require a valid DNS name, but swaps the authentication problem for a host name problem. How are others supposed to know that
github.work.com, which does not exist in DNS, must point to github.com using
Also, for every connection to a previously unknown host, SSH asks for confirmation on first connect. CI/CD tools / Docker images that are doing something related to checking out code from
github.com often have the
authorized_keys pre-populated for
github.com and other prominent services, but most probably not for your alias.