1139

ssh has the -i option to tell which private key file to use when authenticating:

-i identity_file

    Selects a file from which the identity (private key) for RSA or DSA authentication is read.  The default is ~/.ssh/identity for protocol version 1, and ~/.ssh/id_rsa and ~/.ssh/id_dsa for protocol version 2.  Identity files may also be specified on a per-host basis in the configuration file.  It is possible to have multiple -i options (and multiple identities specified in configuration files).

Is there a similar way to tell git which private key file to use on a system with multiple private keys in the ~/.ssh directory?

3

25 Answers 25

1077

In ~/.ssh/config, add:

Host github.com
 HostName github.com
 IdentityFile ~/.ssh/id_rsa_github

If the config file is new, check access permissions using

stat -c %a ~/.ssh/config

if returns NOT 600, you should do

chmod 600 ~/.ssh/config

Now you can do git clone [email protected]:{ORG_NAME}/{REPO_NAME}.git

  • Where {ORG_NAME} is your GitHub user account (or organization account)'s GitHub URI name.
    • Note that there is a colon : after github.com instead of the slash / - as this is not a URI.
  • And {REPO_NAME} is your GitHub repo's URI name
  • For example, for the Linux kernel this would be git clone [email protected]:torvalds/linux.git).

NOTE: On Linux and macOS, verify that the permissions on your IdentityFile are 400. SSH will reject, in a not clearly explicit manner, SSH keys that are too readable. It will just look like a credential rejection. The solution, in this case, is:

chmod 400 ~/.ssh/id_rsa_github
17
  • 278
    What if you need to connect to the same host with different keys? Nov 30, 2012 at 11:24
  • 21
    If the config file is new, don't forget to do chmod 600 ~/.ssh/config
    – elysch
    Mar 15, 2016 at 23:02
  • 13
    @ValentinKlinghammer the answer from @Flimm has the solution for this question. It is to use core.sshCommand git configuration. superuser.com/a/912281/162466 Dec 28, 2017 at 20:47
  • 11
    This forces git to use a specific key for an entire domain, which usually won't work. For example, if someone has a personal github account as well as a work account, this configuration will break one of them.
    – Cerin
    Aug 22, 2019 at 20:09
  • 12
    Regarding contacting the same host with different keys: Here it is essential to understand that in .ssh/config Host is a custom name you can give to the host specified under HostName. The github.com part in the git URL [email protected]:torvalds/linux.git refers to this Host and thus has to match it exactly. If you have a second Github ssh key, you can create a section Host github2 in .ssh/config and then use the URL git@github2:torvalds/linux.git (see also superuser.com/a/1519694/96128).
    – user905686
    Apr 12, 2021 at 17:42
759

Environment variable GIT_SSH_COMMAND

From Git version 2.3.0, you can use the environment variable GIT_SSH_COMMAND like this:

GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa_example" git clone example

Note that -i can sometimes be overridden by your config file, in which case, you should give SSH an empty config file, like this:

GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa_example -F /dev/null" git clone [email protected]:example/example.git

Configuration core.sshCommand

Since Git version 2.10.0, you can configure this per repo or globally, using the core.sshCommand setting. There's no more need to use the environment variable. Here's how you clone a repo and set this configuration at the same time:

git clone -c "core.sshCommand=ssh -i ~/.ssh/id_rsa_example -F /dev/null" [email protected]:example/example.git
cd example/
git pull
git push

If the repo already exists, run:

git config core.sshCommand "ssh -i ~/.ssh/id_rsa_example -F /dev/null"

The configuration is saved in .git/config

19
  • 6
    I had to export the shell variable to an environment variable to make this work, i.e. export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa_example", then git clone example
    – Abdull
    Dec 1, 2015 at 13:46
  • 10
    @Abdull In Bash, doing the assignment on the same line as the command exports the environment variable for just that command. Try it: example=hello /usr/bin/env | grep example.
    – Flimm
    Jan 8, 2016 at 9:50
  • 5
    things have become even better: as of Git 2.10, you can store the command in your Git configuration: stackoverflow.com/a/38474220/520162
    – eckes
    Oct 21, 2016 at 7:57
  • 2
    @Noitidart /dev/null is only a valid filename in UNIX-like operating systems, it doesn't work on Windows.
    – Flimm
    Mar 7, 2017 at 8:19
  • 15
    For me GIT_SSH_COMMAND didn't work until I used IdentitiesOnly, such as this command: GIT_SSH_COMMAND="ssh -i ~/.ssh/mysubdir/id_rsa -o 'IdentitiesOnly yes'" git push. Oct 7, 2018 at 7:13
192

There is no direct way to tell git which private key to use, because it relies on ssh for repository authentication. However, there are still a few ways to achieve your goal:

Option 1: ssh-agent

You can use ssh-agent to temporarily authorize your private key.

For example:

$ ssh-agent sh -c 'ssh-add ~/.ssh/id_rsa; git fetch user@host'

Option 2: GIT_SSH_COMMAND

Pass the ssh arguments by using the GIT_SSH_COMMAND environment variable (Git 2.3.0+).

For example:

$ GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' \
  git clone user@host

You can type this all on one line — ignore $ and leave out the \.

Option 3: GIT_SSH

Pass the ssh arguments by using the GIT_SSH environment variable to specify alternate ssh binary.

For example:

$ echo 'ssh -i ~/.ssh/id_rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $*' > ssh
$ chmod +x ssh
$ GIT_TRACE=1 GIT_SSH='./ssh' git clone user@host

Note: The above lines are shell (terminal) command lines which you should paste into your terminal. They will create a file named ssh, make it executable, and (indirectly) execute it.

Note: GIT_SSH is available since v0.99.4 (2005).

Option 4: ~/.ssh/config

Use the ~/.ssh/config file as suggested in other answers in order to specify the location of your private key, e.g.

Host github.com
  User git
  Hostname github.com
  IdentityFile ~/.ssh/id_rsa
8
  • 3
    // , What if your identity in ssh-agent is forwarded, though, as in this question? superuser.com/questions/971732/… Sep 11, 2015 at 18:06
  • 1
    I've allowed me to reformat this post: IMO this is by far the most comprehensive answer. In its original design, a quick scan suggested the post where describing a single complicated solution to the problem, so I missed it.
    – Alberto
    Jan 21, 2016 at 10:01
  • 10
    $ ssh-agent sh -c 'ssh-add ~/.ssh/id_rsa; git fetch user@host' worked for me when nothing else would. Kudos. Sep 11, 2017 at 15:24
  • 3
    I had to use ~/.ssh/config method, env vars didn't work for me... Sep 22, 2017 at 7:53
  • 2
    GIT_SSH is available since v0.99.4 (August 2005), so basically since Git exists (April 2005).
    – Dominik
    Nov 17, 2017 at 8:10
99

Use custom host config in ~/.ssh/config, like this:

Host gitlab-as-thuc  
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_rsa.thuc
    IdentitiesOnly yes

then use your custom hostname like this:

git remote add thuc git@gitlab-as-thuc:your-repo.git  
10
  • 7
    This is the answer I was looking for, as I have separate GitHub accounts for home and work. I just had to set Host work.github.com HostName github.com IdentityFile ~/.ssh/work, and then replace "github.com" by "work.github.com" whenever I clone a work repository. It still connects to "github.com", but using a non-default key pair.
    – Mikkel
    May 25, 2016 at 17:06
  • 1
    The URL for details ("itblog.study.land/...") doesn't work any more :( Sep 15, 2017 at 8:10
  • 14
    FINALLY!!! This answer actually shows how you can utilize what you put in the ~/.ssh/config file. Every other answer misses how you can set the host when you add the origin, which automatically allows git to use the correct key file. THANK YOU!!
    – Mageician
    Dec 4, 2018 at 15:17
  • 1
    Tip 2) and essential: if you have other Host * also in your config, whereby you want to use that for all over remote machines, except for your gitlab/bitbucket/github, then you need to make sure you exclude that config from Host * and I did this by changing Host * to Host !bitbucket.org which meant that its config doesnt "bleed" into my bitbucket config. See My comment here to the original answer there I want to give credit for here for this idea Dec 29, 2020 at 22:27
  • 1
    To clarify Tip 2) further: from what I've found, it does the same job as Host * !bitbucket.org (which itself didn't work for me) but without the *, so what Host !bitbucket.org(which did work for me) is saying is: " Host * - everything except for bitbucket.org " - the * - meaning "everything", though absent, is still in effect and implied. Dec 29, 2020 at 23:24
99

If you need to connect to the same host with different keys then you can achieve it by:

  1. Configure the ~/.ssh/config with different Hosts but same HostNames.
  2. Clone your repo using the appropriate host.

Example:

~/.ssh/config

Host work
  HostName bitbucket.org
  IdentityFile ~/.ssh/id_rsa_work
  User git
    
Host personal
  HostName bitbucket.org
  IdentityFile ~/.ssh/id_rsa_personal
  User git

Then instead cloning your repos like:

git clone git@bitbucket.org:username/my-work-project.git
git clone git@bitbucket.org:username/my-personal-project.git

you must do

git clone git@work:username/my-work-project.git
git clone git@personal:username/my-personal-project.git
7
  • Or just git remote add origin git@personal:username/my-personal-project.git
    – m8labs
    Dec 19, 2021 at 1:15
  • 2
    This is hands down the way to do it especially if you use deployment keys in Github Dec 27, 2021 at 23:30
  • Excellent. I didn't wanted to remember to modify the default cloning string, so as host I set the same that appears in the original cloning string and works perfect and don't have to clone anything again. Jan 7, 2022 at 17:25
  • 1
    Or you can git remote set-url origin git@personal:username/my-personal-project.git for an existing project
    – Coco
    Feb 14, 2022 at 20:50
  • TortoiseGit does not like this :( Mar 7, 2022 at 14:38
33

Write a script that calls ssh with the arguments you want, and put the filename of the script in $GIT_SSH. Or just put your configuration in ~/.ssh/config.

3
  • 2
    Another explanation of how to do this.
    – Sithsu
    May 12, 2014 at 19:44
  • 1
    ~/.ssh/config Is the way to go.
    – hek2mgl
    May 8, 2015 at 13:56
  • I work on a machine (A) from which I git push to a server (B) that only accepts ssh key authentication. While my ~/.ssh/config setup on (A) works perfectly fine when I work directly on that machine, it does not when I login from some other location (C). Using $GIT_SSH and a script solved this problem. Thanks!
    – bsumirak
    Dec 3, 2015 at 17:28
33

If you do not want to have to specify environment variables every time you run git, do not want another wrapper script, do not/can not run ssh-agent(1), nor want to download another package just for this, use the git-remote-ext(1) external transport:

$ git clone 'ext::ssh -i $HOME/.ssh/alternate_id git.example.com %S /path/to/repository.git'
Cloning into 'repository'
(...)
$ cd repository
$ git remote -v
origin  ext::ssh -i $HOME/.ssh/alternate_id git.example.com %S /path/to/repository.git (fetch)
origin  ext::ssh -i $HOME/.ssh/alternate_id git.example.com %S /path/to/repository.git (push)

I consider this solution superior because:

  • It is repository/remote specific
  • Avoid wrapper script bloat
  • Do not need the SSH agent -- useful if you want unattended clones/push/pulls (e.g. in cron)
  • Definitely, no external tool needed
14
  • 2
    This answer was exactly what I needed to force Chef's git resource to use repository-specific deployment keys to clone/fetch from private Github repositories. The additional advantage of this method over the environment/script based ones is that since the key-path is encoded in the working-repo's config, it will use the same key on both initial clone and subsequent fetches/pushes. Nov 19, 2015 at 16:20
  • 3
    WOW! This is just great, didn't know about this. Thanks for the answer, quite helpful as well in puppet environments, to prevent the extra hassle to manage .ssh/config etc. +1!
    – gxx
    Jun 10, 2016 at 18:36
  • 2
    This solution does not work together with the --recursive flag. The submodules are not fetched using the specified key and therefor fails if they require auth. Dec 21, 2016 at 16:05
  • 1
    Each submodule is an entirely different repository, with their own set of remotes. They're glued together by Git for your convenience, but in no way remotes for a submodule are tied to the ones in the parent repository. I'm afraid that you must set the remote using the ext transport in each submodule for recursion in the parent to work.
    – flaviovs
    Dec 22, 2016 at 19:10
  • 5
    If you encounter the following error fatal: transport 'ext' not allowed, you have to whitelist the ext protocol via the export GIT_ALLOW_PROTOCOL=ext. Basically, the git-remote-ext remote helper (which supports "ext::ssh example.com %S foo/repo" URLs) allows arbitrary command execution. This normally isn't ever a concern because user always sees and trusts the URL they pass to git. However git submodules, through the .gitmodules file, allow an attacker to request the client to fetch arbitrary git URLs. hackerone.com/reports/104465
    – Gomino
    May 13, 2018 at 17:21
23

This is my setup for multiple accounts, each using separate rsa key. It doesn't matter whether it's github, bitbucket, or whatever; this setup is not touching ssh client config, neither it uses hosts as selectors for configuration. It uses directory structure instead, and per-dir-subtree configurations.

$HOME/.gitconfig
[user]
  name = My Public Account Name
  email = [email protected]

[core]
  sshCommand = "ssh -i ~/.ssh/my_public_account_id_rsa"

# public github user - IDEA
[includeIf "gitdir:~/idea/"]
  path = ~/idea/.gitconfig

# org-1 user
[includeIf "gitdir:~/idea/org-1/"]
  path = ~/idea/org-1/.gitconfig

# org-2 user
[includeIf "gitdir:~/idea/org-2/"]
  path = ~/idea/org-2/.gitconfig

# public github user - ruby
[includeIf "gitdir:~/rubymine/"]
  path = ~/rubymine/.gitconfig

# org-1 user
[includeIf "gitdir:~/rubymine/org-1/"]
  path = ~/rubymine/org-1/.gitconfig

And if I need to override key and user details for some projects, then I keep them in the same dir. And in that dir I create a ".gitconfig" file, like:

$HOME/idea/org-1/.gitconfig
[user]
  name = My Org-1 Account Name
  email = [email protected]

[core]
  sshCommand = "ssh -i ~/.ssh/my_org1_account_id_rsa"

Whenever I run any git command in ~/idea/org-1 directory or its sub-dirs, then it picks up my org-1 specific config and uses my_org1_account_id_rsa for ssh. And another name/email also for commits, not only rsa key.

This is hierarchical configuration, i.e. configurations are evaluated from more generic to more specific one. This is achieved by ordering "includeIf" clauses in your $HOME/.gitconfig and having trailing slash in glob patterns, which results in adding ** at the end. It's described nicely in documentation gitconfig includeIf docs. So keep in mind that order matters, and glob patterns matter.

4
  • 1
    Great, this worked super well for me as I already had git config files for work and personal projects.
    – SamAko
    Jan 10, 2022 at 10:45
  • 2
    Simplest way to achieve custom keys per project, this should be the accepted answer Aug 8, 2022 at 7:29
  • 2
    This is absolutely the best answer going! Thanks
    – charrison
    Apr 19, 2023 at 20:55
  • I liked it at first and it works in general -- although i used [includeIf "hasconfig:remote.*.url:[email protected]:my-account/**"]. However, cloning is not working with this nor is it with gitdir
    – elonderin
    Jan 12 at 19:30
18

After my struggle with $GIT_SSH I would like to share what worked for me.

Through my examples I will assume you have your private key located at/home/user/.ssh/jenkins

Error to avoid: GIT_SSH value includes options

$ export GIT_SSH="ssh -i /home/user/.ssh/jenkins"

or whatever similar will fails, as git will try to execute the value as a file. For that reason, you have to create a script.

Working example of $GIT_SSH script /home/user/gssh.sh

The script will be invoked as follows:

$ $GIT_SSH [username@]host [-p <port>] <command>

Sample script working could look like:

#!/bin/sh
ssh -i /home/user/.ssh/jenkins $*

Note the $* at the end, it is important part of it.

Even safer alternative, which would prevent any possible conflict with anything in your default config file (plus explicitly mentioning the port to use) would be:

#!/bin/sh
ssh -i /home/user/.ssh/jenkins -F /dev/null -p 22 $*

Assuming the script is in /home/user/gssh.sh, you shall then:

$ export GIT_SSH=/home/user/gssh.sh

and all shall work.

4
  • Thanks. Just note: use "$@" instead of $* for pass-thru arguments, as the former behaves correctly when arguments contain whitespace. Mar 31, 2016 at 7:39
  • @PiotrFindeisen Thanks for your note. However, I do not understand it completely - in zsh it helps me to keep strings with space in one piece, but in bash not. Can you tell me more or point to some explanation? I do not want to add some modification blindly. Mar 31, 2016 at 10:33
  • You should remove the first half of your answer. No one's interested in a solution that doesn't work, and it's wasted reading that obfuscates the correct answer at the bottom, which works wonderfully.
    – Cerin
    Aug 3, 2018 at 16:47
  • @Cerin If you mean removing the "Error to avoid" I am going to keep it there. It shares common pitfall to avoid and it is very short. I am sure, someone would try optimizing the solution by providing all the things into variable (this happened to me), so I tried to shorten the path to success. Aug 4, 2018 at 20:14
11

So I set the GIT_SSH env variable to $HOME/bin/git-ssh.

In order to support having my repo configuration dictate which ssh identity to use, my ~/bin/git-ssh file is this:

#!/bin/sh
ssh -i $(git config --get ssh.identity) -F /dev/null -p 22 $*

Then I have a global git config setting:

$ git config --global ssh.identity ~/.ssh/default_id_rsa

And within any git repository I can just set a local ssh.identity git config value:

$ git config --local ssh.identity ~/.ssh/any_other_id_rsa

Voila!

If you can have a different email address for each identity, it gets even simpler, because you can just name your keys after your email addresses and then have the git config's user.email drive the key selection in a ~/bin/git-ssh like this:

#!/bin/sh
ssh -i $HOME/.ssh/$(git config --get user.email) -F /dev/null -p 22 $*
8

I had a client that needed a separate github account. So I needed to use a separate key just for this one project.

My solution was to add this to my .zshrc / .bashrc:

alias infogit="GIT_SSH_COMMAND=\"ssh -i ~/.ssh/id_specialkey\" git $@"

Whenever I want to use git for that project I replace "infogit" with git:

infogit commit -am "Some message" && infogit push

For me, it's easier to remember.

6

You can just use ssh-ident instead of creating your own wrapper.

You can read more at: https://github.com/ccontavalli/ssh-ident

It loads ssh keys on demand when first needed, once, even with multiple login sessions, xterms or NFS shared homes.

With a tiny config file, it can automatically load different keys and keep them separated in different agents (for agent forwarding) depending on what you need to do.

4

Generally, you want to use ~/.ssh/config for this. Simply pair server addresses with the keys you want to use for them as follows:

Host github.com
  IdentityFile ~/.ssh/id_rsa.github
Host heroku.com
  IdentityFile ~/.ssh/id_rsa.heroku
Host *
  IdentityFile ~/.ssh/id_rsa

Host * denotes any server, so I use it to set ~/.ssh/id_rsa as the default key to use.

4

I build on @shellholic and this SO thread with a few teaks. I use GitHub as an example and assume that you have a private key in ~/.ssh/github (otherwise, see this SO thread) and that you added the public key to your GitHub profile (otherwise see GitHub's help).

If needed, create a new SSH config file at ~/.ssh/config and change permissions to 400

touch ~/.ssh/config
chmod 600 ~/.ssh/config

Add this to the ~/.ssh/config file:

Host github.com
    IdentityFile ~/.ssh/github
    IdentitiesOnly yes

If you already have a remote set up, you may want to delete it, otherwise you may still be prompted for username and password:

git remote rm origin

Then add a remote to the git repository, and notice the colon before the user name:

git remote add origin [email protected]:user_name/repo_name.git

And then git commands work normally, e.g.:

git push origin master
git pull origin 

@HeyWatchThis on this SO thread suggested adding IdentitiesOnly yes to prevent the SSH default behavior of sending the identity file matching the default filename for each protocol. See that thread for more information and references.

1
  • This was my mistake: "If you already have a remote set up...". Thanks a lot!!! Oct 4, 2018 at 17:43
3

My solution was this:

create a script:

#!/bin/bash
KEY=dafault_key_to_be_used
PORT=10022 #default port...
for i in $@;do
   case $i in
    --port=*)
        PORT="${i:7}";;
    --key=*)KEY="${i:6}";;
   esac
done
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/${KEY} -p ${PORT}"
echo Command: $GIT_SSH_COMMAND

then when you have to change the var run:

. ./thescript.sh [--port=] [--key=]

Don't forget the extra dot!! this makes the script set the environments vars!! --key and --port are optional.

2

All the information up to now (2020-01) are useful, but IMHO are a little bit messy, and no post sums them up to a clean solution.

So here are my solutions. If you are lucky with your current solution, then you are free to ignore this post.

Overall Premise:
Solution for *nix-based OS. Tested on Debian-based OS, but should work on other *nix-based OS.

Overall Prerequisites:

  • Separate identity files (keys) for different hosts and/or users available, e.g. generated via ssh-keygen -f <filename> ...

Goals:

  • Use a specific identity file (key) per repository (optional: per host)
  • Works with any host: GitHub, GitLab, BitBucket, etc
  • Simple to use and to adopt
  • Prefer not messing around with URLs and use them as-is
  • Prefer to use normal configuration of git and ssh
  • (nice-to-have) Working in shell and gui tools
  • (nice-to-have) Works with any tool: git, ssh, svn, rsync, etc.

Solutions:

  1. Convenient tool ssh-ident
  2. Just normal git and ssh, with a minor portition of shell magic
  3. Host renaming (also works with Putty/Pageant/Plink)

Note: A Match Path feature for OpenSSH would not help here, see https://marc.info/?l=openssh-unix-dev&m=141833748901966&w=2

Solution #1 - Tool ssh-ident
Notes:

  • Currently (as of 2020-01) I link my own fork of the original ssh-ident, as it gives more flexibility to argument and key filename matching.
  • See docstring inside ssh-ident for how to use

Prerequisites:

  • none

Download and prepare

  • Download first to a project directory, then install to /usr/local/bin
    DLDIR="${HOME}/work/packages/ssh-ident"
    [ -d "${DLDIR}" ] || mkdir -vp "${DLDIR}"
    #
    #original: DLURL='https://raw.githubusercontent.com/ccontavalli/ssh-ident/master'
    DLURL='https://raw.githubusercontent.com/ssh-ident/ssh-ident1/dev'
    wget -P "${DLDIR}" -N "${DLURL}/ssh-ident"
    unset -v DLURL
    #
    install -v -D -t /usr/local/bin "${DLDIR}/ssh-ident"
    ## Debian base >=11 "bullseye": sed -i -e '1 s#\(\spython\)\(\s\|$\)#\13\2#' /usr/local/bin/ssh-ident
    #
    unset -v DLDIR
    

Configure ssh-ident and the identities

  • Prepare files and directories
    [ -f ~/.ssh-ident ] || printf -- '\n' >~/.ssh-ident
    MYEXTRAIDENTITIES='1 gh2 dummy'
    for MYIDENTITY in ${MYEXTRAIDENTITIES}; do
      echo "${MYIDENTITY}"
      [ -d ~/.ssh/identities/"${MYIDENTITY}" ] || mkdir -vp ~/.ssh/identities/"${MYIDENTITY}"
    done
    chmod -v -R u=rwX,go= ~/.ssh/identities
    unset -v MYEXTRAIDENTITIES MYIDENTITY
    
  • Maintain configuration of ssh-ident to detect GitHub repositories
    Example: ~/.ssh-ident
    ...
    MATCH_ARGV = [
      (r"\s(git@)?github\.com\s.*'my-git-user-1\/dummy\.git'", "dummy"),
      (r"\s(git@)?github\.com\s.*'my-git-user-1\/.+\.git'", "1"),
      (r"\s(git@)?github\.com\s.*'my-git-user-2\/.+\.git'", "gh2"),
      ...
      #
      (r"\s(git@)?gist\.github\.com\s.*'abcdef01234567890fedcba912345678\.git'", "1"),
      ...
    ]
    ...
    
  • Copy private keys plus their public keys(!) to the corresponding identity directory
  • Optionally create a per-identity ssh config file
    Example: ~/.ssh/identities/1/config
    Host github.com,gist.github.com
        IdentitiesOnly yes
        IdentityFile ~/.ssh/identities/1/github-user1.key
        User git
    

Enable ssh-ident

  • Setup as ssh wrapper by installing as ssh via symbolic link to ssh-ident, either in /usr/local/bin for all users or in a user bin directory
    ln -s -T /usr/local/bin/ssh-ident ~/bin/ssh
    
  • Testing ssh-ident config. Pass fitting parameters to the SSH call.
    ssh -vT [email protected] "test 'my-git-user-1/test-repo.git'"
    
  • Use git as normal.

Solution #2 - Just normal git and ssh
Prerequisites:

  • git version 2.10+
    • git config core.sshCommand (2.10+)
  • OpenSSH client version 6.5+
    • function Match (6.5+)
    • option IdentityFile none (6.3+)
    • option IdentitiesOnly (3.9+)

Concept:
Use OpenSSH client config's Match Exec functionality to check for an environment variable (e.g. SSHGITUSER) to select a specific identity file (key). Fail when environment variable is not set or contains an unknown user id.

Allows for the following options:

  • One account on host: use Host block in OpenSSH client config to directly define the IdentityFile = pure vanilla ssh standard, no environment variable needed
  • Multiple accounts on a host: pass the environment variable to the OpenSSH client, so it can react on it within Match blocks
    • Either set the environment variable directly in front of the git call (e.g. SSHGITUSER='<id>' git ...)
      • Has to be used when cloning a git repository (e.g. SSHGITUSER='<id>' git clone ...)
      • Works also for remotes that need different identity files (e.g. SSHGITUSER='<id>' git fetch <remote>)
      • Works also when using environment variables GIT_SSH_COMMAND or GIT_SSH (see docs)
    • or use git config core.sshCommand to hard-code it for a repository (e.g. git config --local core.sshCommand "SSHGITUSER='<id>' ssh")
      • Does not work for remotes that need different identity files
      • Does not work when using environment variables GIT_SSH_COMMAND or GIT_SSH (see docs) as these override git config core.sshCommand of the repository

Setting up OpenSSH client config ssh_config:
Whenever we connect via ssh to a host, then we only want to connect with the identity files we explicitly specify. This can be achieved by setting IdentitiesOnly to yes and IdentityFile to none in a matching Host block, then no other identity files are offered to the target server, even when cached inside ssh-agent, nor the default key definitions will be tried.
If a default Host block exists for all servers, then extend its pattern to exclude the special host there.
Select the wanted identity file by checking an environment variable (e.g. SSHGITUSER). Define an id (number, letter or name) for each user and create a Match block for each id. The Match block should check the Host first to avoid using the identity file for other hosts and to avoid useless calls to the shell for testing the environment variable.
Specifing the user git for github.com and gist.github.com allows to remove git@ from the URL. This setting is overriden when a user is defined in the URL.

Example ~/.ssh/config for GitHub:

...
### >>> GitHub
## use: SSHGITUSER='<id>' git ...
## optional: git config --local core.sshCommand "SSHGITUSER='<id>' ssh"
Host github.com,gist.github.com
    IdentitiesOnly yes
    IdentityFile none
    User git

Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = '1'"
    IdentityFile ~/.ssh/github-user1.key

Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = 'gh2'"
    IdentityFile ~/.ssh/github-user2.key

Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = 'dummy'"
    IdentityFile ~/.ssh/github-dummy-fake.key
### <<< GitHub
...
Host !github.com,!gist.github.com,*
    IdentityFile ~/.ssh/personal.key
...

Notes:

  • If only a single identity file is used for a host, then in the related Host block change IdentityFile from none directly to the identity file and no Match blocks are needed.
  • Remote User can also be set in a Match block. Remember a user in the URL overrides this setting.

Testing ssh config:
Pass the environment variable for the SSH call by defining it directly in front of the command. Use a wrong user in the URL to avoid actual logins as we just want to see the identity files offered to the server.

SSHGITUSER='1' ssh -vT [email protected]

Setting up git config of a repository for a hard-coded user id:
To hard-code the user id to a repository use git config core.sshCommand on the repository itself (via --local) to pass the environment variable to the ssh command.
The ssh command for git is either defined by the environment variable GIT_SSH_COMMAND (since git 2.3) or git config core.sshCommand (since git 2.10). If both are empty, then it normally defaults to just ssh (see Code funtions get_ssh_command() and fill_ssh_args() in connect.c).
Special case is when cloning a repository, then there is no config for the not yet cloned repository, and the environment variable has to be set directly in front of the git call (e.g. SSHGITUSER='<id>' git clone ...).

Example:

SSHGITUSER='1' git clone [email protected]:my-git-user-1/test-repo.git ~/work/test-repo
cd ~/work/test-repo
git config --local core.sshCommand "SSHGITUSER='1' ssh"
# Test: git fetch

Notes:
When using environment variables GIT_SSH_COMMAND or GIT_SSH (see docs) these will override git config core.sshCommand of the repository.
Check first via set | grep -e '^GIT_SSH' and if any is set, then adapt from using environment variables to git config core.sshCommand of the user (via --global) and remove the GIT_SSH[_COMMAND] variable.
(e.g. git config --global core.sshCommand "<content of used GIT_SSH[_COMMAND] variable>", plus git config --local core.sshCommand "SSHGITUSER='<id>' <content of used GIT_SSH[_COMMAND] variable>")

Other ideas for #2:

  • Use environment variable for IdentityFile ~/.ssh/github-${SSHGITUSER}.key or User ${SSHGITUSER}. Has the tendency to longer user ids.

Setting up other tools for #2:
On the command line just pass the environment variable by defining it directly in front of the command.
For hard-coding the user id the task is to find out how to pass the environment variable to the tool, e.g. configuration, wrapper script, alias, etc.

Solution #99 - Host renaming
Prerequisites:

  • none

Concept:
As also mentioned in this thread you can rename the host as described in the git FAQ for the ssh config to recognize it, choose the identity depending on it and set the correct host name.
I normally do not prefer to mess up URLs instead of using configurations/variables. Still I would then add the user id as a subdomain, e.g. <user id>.github.com.
After some talk adding the user id behind the host has the advantage that normally DNS resolution will fail as the TLD got messed up and therefore the connection will immediately fail also. Extended the example for this, e.g. github.com-<user id>.

This also works for Plink and Pageant from Putty. Putty checks for a saved session named like the "host", e.g. session github.com-<user id>. This can be used for this solution.
Then Putty checks for a session which references the host, e.g. session testing with host github.com. Just to let you know.

Example ~/.ssh/config for GitHub:

...
### >>> GitHub
Host github.com,gist.github.com,*.github.com,*.gist.github.com,github.com-*,gist.github.com-*
        IdentitiesOnly yes
        IdentityFile none
        User git

Host 1.github.com,github.com-1
        IdentityFile ~/.ssh/github-user1.key
        Hostname github.com

Host 1.gist.github.com,gist.github.com-1
        IdentityFile ~/.ssh/github-user1.key
        Hostname gist.github.com

Host gh2.github.com,github.com-gh2
        IdentityFile ~/.ssh/github-user2.key
        Hostname github.com

Host gh2.gist.github.com,gist.github.com-gh2
        IdentityFile ~/.ssh/github-user2.key
        Hostname gist.github.com

### <<< GitHub

Host !github.com,!gist.github.com,*
   IdentityFile ~/.ssh/personal.key
...
1
1

While the question doesn't request it, I am including this answer for anyone else looking to solve the same problem just specifically for .

The gitlab solution

I tried using the approach, but even the git documentation recommends using ~/.ssh/config for anything more than the simple case. In my case I am pushing to a server - and I wanted to do so as a specific user - which is of course defined by the during and not the username git. Once implemented I simply perform the following:

~/myrepo> git mycommit -m "Important Stuff"
~/myrepo> git mypush
[proceed to enter passphrase for private key...]

Setup

Recall the location of your /myfolder/.ssh/my_gitlab_id_rsa in my case.

Add an entry in ~/.ssh/config:

Host gitlab-delegate
    HostName gitlab.mydomain.com
    User git
    IdentityFile /myfolder/.ssh/my_gitlab_id_rsa
    IdentitiesOnly yes

Add the in ~/.gitconfig:

mypush = "!f() { \
           path=$(git config --get remote.origin.url | cut -d':' -f2); \
           branch=$(git rev-parse --abbrev-ref HEAD); \
           git remote add gitlab_as_me git@gitlab-delegate:$path && \
           git push gitlab_as_me $branch && \
           git pull origin $branch; \
           git remote remove gitlab_as_me; \
         }; f"

As a bonus, I perform my commits on this same host as a specific user with this :

mycommit = "!f() { \
             git -c "user.name=myname" -c "[email protected]" commit \"$@\"; \
           }; f"

Explanation

All of the above assumes the relevant remote is origin and the relevant branch is currently checked out. For reference I ran into several items that needed to be addressed:

  • The solution requires creating a new remote gitlab_as_me, and I didn't like seeing the extra remote hanging around in my log tree so I remove it when finished
  • In order to create the remote, there is a need to generate the remote's url on the fly - in the case of gitlab this was achieved with a simple bash
  • When performing a push to gitlab_as_me you need to be specific about what branch you are pushing
  • After performing the push your local origin pointer needs to be "updated" in order to match gitlab_as_me (the git pull origin $branch does this)
1

Just use ssh-agent and ssh-add commands.

# create an agent
ssh-agent

# add your default key
ssh-add ~/.ssh/id_rsa

# add your second key
ssh-add ~/.ssh/<your key name>

After executing the above commands, you can use both keys as same time. Just type

git clone [email protected]:<yourname>/<your-repo>.git

to clone your repository.

You need to execute the above command after you reboot your machine.

3
  • Please explain the process including How can I create an agent
    – Srikrushna
    Apr 6, 2019 at 20:20
  • Sounds smart, - but maybe a little to good to be true, considering how clumbsy all the other answers are. I couldn't get this to work.
    – Zeth
    Aug 15, 2020 at 9:48
  • @Zeth You may have a problem when creating the ssh agent. If you are using MacOS, you only need to execute ssh-agent. If you are using Linux, I suggest you read this article wiki.archlinux.org/index.php/SSH_keys#ssh-agent Dec 26, 2020 at 17:53
1
    # start :: how-to use different ssh identity files

    # create the company identity file
    ssh-keygen -t rsa -b 4096 -C "[email protected]"
    # save private key to ~/.ssh/id_rsa.corp, 
    cat ~/.ssh/id_rsa.corp.pub # copy paste this string into your corp web ui security ssh keys

    # create your private identify file
    ssh-keygen -t rsa -b 4096 -C "[email protected]"
    # save private key to ~/.ssh/id_rsa.me, note the public key ~/.ssh/id_rsa.me.pub
    cat ~/.ssh/id_rsa.me.pub # copy paste this one into your githubs, private keys

    # clone company internal repo as follows
    GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa.corp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
    git clone [email protected]:corp/project.git

    export git_msg="my commit msg with my corporate identity, explicitly provide author"
    git add --all ; git commit -m "$git_msg" --author "MeFirst MeLast <[email protected]>"
    GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa.corp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
    git push 
    # and verify 
    clear ; git log --pretty --format='%h %ae %<(15)%an ::: %s

    # clone public repo as follows
    GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa.corp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
    git clone [email protected]:acoolprojectowner/coolproject.git

    export git_msg="my commit msg with my personal identity, again author "
    git add --all ; git commit -m "$git_msg" --author "MeFirst MeLast <[email protected]>"
    GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa.me -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
    git push ; 
    # and verify 
    clear ; git log --pretty --format='%h %ae %<(15)%an ::: %s

    # stop :: how-to use different ssh identity files
1

There are already a great many proposed solutions here, including numerous options working around the GIT_SSH_COMMAND environment variable, many of which I've actually used myself over the years.

However, I would like to share a much simpler process that I recently discovered, using only direnv.

My personal favourite point of this method: it requires no modification of any Git config files, but it also won't conflict with any modifications you may want/need/have..


Note: I'm going to approach this with one "primary" SSH key to use by default, and one "secondary" SSH key to use for specific projects. This will fit in well with doing "work" things on a "personal" machine, or vice versa, as well as extending to any additional "special" SSH keys you may need.

Also, I should note that I use this for both GitHub and GitLab, with a "work" and "personal" SSH keys on each platform, from multiple separate computers, and it "Just Works"(tm).

The following steps do make a few assumptions here:

  • you're running in a Unix-like environment (Linux, macOS, WSLv2 on Windows)
  • primary SSH key in ~/.ssh/id_ed25519
  • secondary SSH key in ~/.ssh/id_ed25519_personal
  • work workspace in ~/workspace
  • personal workspace in ~/personal

With that in place, the configuration process itself is dead simple:

  1. install direnv, ensure it's loaded by .bashrc/.zshrc/.profile/etc.
  2. create a file at ~/personal/.envrc with export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_personal"
  3. open terminal at ~/personal
  • at this point, you might see a message like direnv: error /Users/$USER/personal/.envrc is blocked. Run direnv allow to approve its content. note: you will see this message every time the target .direnv file is modified.

At this point, navigating into ~/personal in your terminal (or any subfolder, even directly) will load environment variables from .envrc, and navigating out to anywhere else will unload them.


Here's an example of this in action on macOS running ZSH enhanced with OMZ (bonus: OMZ comes bundled with a direnv plugin, which adds the binary to your $PATH and shows it's "active" status in your terminal window):

 ~ ------------------------------------------------------------------- 11:00:33 
> cd workspace 

 ~/workspace --------------------------------------------------------- 11:00:39 
> echo $GIT_SSH_COMMAND


 ~/workspace --------------------------------------------------------- 11:00:40 
> cd ../personal       
direnv: loading ~/personal/.envrc
direnv: export +GIT_SSH_COMMAND

 ~/personal ------------------------------------------------- direnv | 11:00:47 
> echo $GIT_SSH_COMMAND
ssh -i ~/.ssh/id_ed25519_personal

 ~/personal ------------------------------------------------- direnv | 11:00:49 
> cd ..
direnv: unloading

 ~ ------------------------------------------------------------------- 11:00:52 
> echo $GIT_SSH_COMMAND


 ~ ------------------------------------------------------------------- 11:00:53 
>

Hope this helps some people.. (:

1

Before calling your usual git commands run:

eval $(ssh-agent)
ssh-add ~/.ssh/your_spesific_private_key
1
  • 2
    You answer is really short for a very old question that has many documented answers.
    – Toto
    Sep 24, 2021 at 15:33
1

All of the answers above are correct, but they all fail, when using something like terraform

Imaginge this scenario

  1. You have a personal github account
  2. You join a new company, and create another github account with your work email

If you follow the instructions above, in 99% of the time, they will work. However, let's say you use terraform, with private git repos, and you have terraform modules and all of these modules are defined as

module "module_name" {
  source                     = "[email protected]:company/terraform-modules-repository.git//module_name?ref=v1.0.1"
  ..
}

Then when terraform tries to fetch these modules, they will fail. This is the problem.

Your constraints are

  1. You cannot modify the source code
  2. You will have to swap your private and work keys in your ssh config

See the problem?

Here is a better solution, that will work for many combinations

  1. You define a gitconfig, that allows you to override your gitconfig See https://git-scm.com/docs/git-config#_includes
$ cat ~/.gitconfig                              
[user]
    email = [email protected]
    name = Name Surname
[includeIf "gitdir:**/company_name/"]
    path = ~/.git-preferences/work/company_name/.gitconfig
  1. Inside of your preferences file for your company you add your ssh key to be used
$ cat ~/.git-preferences/work/company_name/.gitconfig
[user]
    email = [email protected]
[core]
    sshCommand = ssh -i ~/.ssh/namesurname-company
  1. With terraform specefically, it ignores the ssh command in the git config, and it will only work if you set the env variable https://github.com/hashicorp/terraform/issues/19232 Therefore you will also need to issue the command
export GIT_SSH_COMMAND="ssh -i ~/.ssh/namesurname-company"

This is the only way that I could get it working for all cases

0

I'm using git version 2.16 and I don't need a single piece of script not even a config or modified commands.

  • Just copied my private key to .ssh/id_rsa
  • set permissions to 600

And git reads to key automatically. I doesn't ask anything and it doesn't throw an error. Just works fine.

1
  • 3
    Did you notice that the question is about “a system with multiple private keys in the ~/.ssh directory”? Jun 6, 2018 at 5:16
0

When you have multiple git account and you want different ssh key

You have to follow same step for generating the ssh key, but be sure you

ssh-keygen -t ed25519 -C [email protected]

Enter the path you want to save(Ex: my-pc/Desktop/.ssh/ed25519)

Note: ed25519 is the folder name

Add the public key to your gitlab (How to adding ssh key to gitlab)

You have to new ssh identity using the below comand

ssh-add ~/my-pc/Desktop/.ssh/ed25519
1
  • 1
    (1) Are you quoting somebody or something? If so, please identify the source. If not, please don’t use quote formatting.  (2) What is “ed25519”? … … … … … … … … … … … Please do not respond in comments; edit your answer to make it clearer and more complete. Apr 7, 2019 at 0:35
0

For MacOS:

git config core.sshCommand "ssh -i ~/.ssh/id_rsa_2"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .