1. Introduction

The Secure Shell (SSH) protocol is famous for its almost impenetrable security with the proper use of keys. After configuring public key authentication, we can simply use our private keys (identities) to access remote servers. But which private key do we use for a given session?

In this tutorial, we explore several approaches to using multiple private keys for Linux SSH access. First, we briefly mention the obvious manual method. Next, we turn to the local $HOME/.ssh/config client configuration options, which can also be applied globally in ssh_config. Of course, all configuration methods require adding and maintaining proper records. Finally, we discuss automated key managers and custom SSH calls as possible ways to organize multi-key usage.

For brevity and security reasons, we only consider the newest iteration of SSH version 2 (SSHv2) as implemented by OpenSSH. Also, the -v verbose switch of the SSH client is used for verification. Most options apply to interactive connections, but also scp and other clients.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4 and OpenSSH 8.4p1. It should work in most POSIX-compliant environments.

2. Manual Choice

With a library of private keys, we can always use the real-life approach of manually picking the correct key for our needs:

$ ssh -i KEY_PATH xost

Here, we employ the -i switch of our SSH client to specify the KEY_PATH. In fact, similar options exist for most clients. Further, there are commonly default key paths when none are specified:

$ ssh -v xost
debug1: Will attempt key: /home/baeldung/.ssh/id_rsa
debug1: Will attempt key: /home/baeldung/.ssh/id_dsa
debug1: Will attempt key: /home/baeldung/.ssh/id_ecdsa
debug1: Will attempt key: /home/baeldung/.ssh/id_ecdsa_sk
debug1: Will attempt key: /home/baeldung/.ssh/id_ed25519
debug1: Will attempt key: /home/baeldung/.ssh/id_ed25519_sk
debug1: Will attempt key: /home/baeldung/.ssh/id_xmss

While easier and more straightforward than others for a small number of keys, this option can get tedious very fast. With many keys in different paths, organizing all of them properly and by purpose might even make indexing necessary.

In any case, at one point, an organization is essential.

3. Based on Priority

To avoid having to specify a key each time, we can list them all in any of the SSH client configuration files:

$ cat $HOME/.ssh/config
IdentityFile ~/.ssh/custom_id
IdentityFile ~/.ssh/xost_id
IdentityFile ~/.ssh/test
$ ssh -v xost
debug1: Will attempt key: /home/baeldung/.ssh/custom_id  explicit
debug1: Will attempt key: /home/baeldung/.ssh/xost_id  explicit
debug1: Will attempt key: /home/baeldung/.ssh/test  explicit
debug1: Trying private key: /home/baeldung/.ssh/xost_id
no such identity: /home/baeldung/.ssh/xost_id: No such file or directory

In this case, we see an explicit key specification with IdentityFile similar to the -i switch, so no defaults were tried. In addition, unlike the situation with the hard-coded defaults, there is a notification when a key is missing from a given path.

The global key list is iterated as is, so the priority of a key is relative to its row number. On the negative side, each identity will be checked and tried on every connection. This can lead to several issues:

  • incorrect key applied to a given connection, i.e., security risk
  • each key is checked and tested on every connection, leading to possible slowdowns
  • priority is one-dimensional, which might not be enough for many scenarios involving multiple private keys

Although we can use IdentitiesOnly to limit the exposure, let’s try to avoid these potential drawbacks with other methods.

4. Hostbased Selection

The OpenSSH client configuration supports Host blocks for defining host-specific options linked to a given internal name:

$ cat $HOME/.ssh/config
Host xost-main
  Hostname xost
  IdentityFile ~/.personal/xost_id
  User baeldung
$ ssh -v xost-main
debug1: Reading configuration data /home/baeldung/.ssh/config
debug1: /home/baeldung/.ssh/config line 1: Applying options for xost-main
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files
debug1: /etc/ssh/ssh_config line 21: Applying options for *
debug1: Connecting to xost [] port 22.
debug1: Connection established.
debug1: Authenticating to as 'baeldung'
debug1: Trying private key: /home/baeldung/.personal/xost_id

Here, ssh applies the options for the name xost-main from the local configuration file:

  • Hostname xost []
  • User baeldung
  • key /home/baeldung/.personal/xost_id

Moreover, we can have multiple hosts in our configuration, each with its own internal name and settings:

$ cat $HOME/.ssh/config
Host xost-main
  Hostname xost
  IdentityFile ~/.personal/xost_id
  User baeldung
Host xost-dev
  Hostname xost
  IdentityFile ~/.dev/xost_dev_id
  User dev

Now, invoking ssh xost-dev would use the options from the last three lines of the file. There are many other host-specific options, which we can apply in each Host block.

Alternatively, we can depend on an external mechanism to keep and apply keys as necessary.

5. ssh-agent and Key Managers

The concept of an agent in the realm of SSH is similar to that of any other manager, but for keys. There are different variants such as ssh-agent, gnome-keyring, and others.

In most cases, there is a minimal set of operations a key manager or agent can perform:

  • add key or key pair
  • remove key
  • get keys
  • sign message
  • lock or unlock with a passphrase

Essentially, key managers and agents are a secure datastore for private keys for use without delay and explicit identity specification:

$ ssh-agent bash
$ ssh-add /path/to/private.key
$ ssh-add -l
3072 SHA256:RiQ[...]8Nk baeldung@xost (RSA)
$ ssh -v xost
debug1: Offering public key: baeldung@xost RSA SHA256:RiQ[...]8Nk agent
debug1: Server accepts key: baeldung@xost RSA SHA256:RiQ[...]8Nk agent
debug1: Authentication succeeded (publickey).

In this regard, setting the IdentitiesOnly option of ssh to yes limits the keys supplied for a given connection to avoid sending every identity. Moreover, we can further enhance the functionality of agents.

In fact, projects like keychain and ssh-ident act as keychains and a front-end to ssh-agent. Keychains avoid the need to manually add or remove private keys, instead loading them as configured. This is an upgrade to the AddKeysToAgent option.

Of course, similar to using custom agent UNIX sockets with IdentityAgent, we can use custom ssh commands from third-party software.

6. Custom Command Options (Git)

Regardless of whether an application has its own SSH implementation or just calls one, we can sometimes decide on a private key to apply in the application that uses it.

For instance, Git provides the core.sshCommand option for its repositories. Naturally, we can simply use it as a command-line option:

$ git clone -c "core.sshCommand=ssh -i ~/.ssh/work_id"

On the other hand, we can persist the option for our repository with git config or in its configuration file:

$ git config core.sshCommand "ssh -i ~/.ssh/work_id"

This way, we always select a specific key for a given repository.

7. Summary

In this article, we talked about ways to organize the use of multiple private keys with SSH. Generally, we discussed several strategies and how to apply them in practice.

In conclusion, we can always select a private key manually, but, depending on the scenario and security requirements, we can also employ tools and configurations to prioritize key usage.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.