Presumably ssh tries to access the fd several times, but the fd is destroyed after it has been read the very first time:

# ssh -i <(echo $KEY) [email protected]
Warning: Identity file /dev/fd/11 not accessible: Bad file descriptor.
[email protected]: Permission denied (publickey).

Is there any other way without writing/removing temporary file?

  • The tool for handling keys is called an agent. You would add the contents of your variable to the agent and ssh would get it from the agent. Therefore this answer on Stack Overflow is your solution
    – Bananguin
    Commented Apr 23, 2019 at 12:14
  • 1
    Agent is not always available, also I want a solution which would work well in scripts.
    – NarūnasK
    Commented Apr 23, 2019 at 12:22
  • 9
    The agent approach does work well in scripts and ssh-agent is usually part of ssh installations. If you have special constraints you should put them in your question, because we cannot guess those.
    – Bananguin
    Commented Apr 23, 2019 at 12:27
  • 2
    This feels like a really bad idea. Can you explain the context here. What are you trying to achieve? Why have you rejected the idea of storing keys in files? Commented Apr 23, 2019 at 12:50
  • Where does the contents of $KEY come from?
    – Kusalananda
    Commented Apr 23, 2019 at 13:55

4 Answers 4

export MYKEY=`cat key.pem`
ssh-add - <<< "$MYKEY"
ssh [email protected]
  • 4
    Noting the OP's resistance to using an agent
    – Jeff Schaller
    Commented Dec 13, 2019 at 19:11
  • Correct, it must not depend on the ssh-agent.
    – NarūnasK
    Commented Dec 14, 2019 at 13:19
  • 1
    ssh-add - <<< "$MYKEY" requires a passphrase: Enter passphrase for (stdin): The key is saved without a passphrase... Commented May 21, 2020 at 7:51
  • I get: Error loading key "(stdin)": error in libcrypt
    – jaques-sam
    Commented May 30 at 8:41

tl;dr No, but see The XY Problem.

$ strace -f ssh -i <(cat ~/.ssh/id_rsa) user@server echo test
newfstatat(AT_FDCWD, "/dev/fd/63", 0x7ffc4f1d0c60, 0) = -1 ENOENT (No such file or directory)
write(2, "Warning: Identity file /dev/fd/6"..., 77Warning: Identity file /dev/fd/63 not accessible: No such file or directory.) = 77

So for some reason at the beginning ssh closes the fd. Maybe it closes all but the standard descriptors.

In any case, your assumption is wrong:

Presumably ssh tries to access the fd several times, but the fd is destroyed after it has been read the very first time

There's just one syscall dealing with the fd before trying to stat it: close(63). And yep, after that it gets destroyed.


Here is a solution, which uses ssh-agent, doesn't need it running.

ssh-agent bash -c "ssh-add <(echo '$MY_SSH_KEY') && ssh ..."

So the agent will not be "daemonized", but will only live as long as your SSH session is alive.


As an alternative, if writing the key into a temporary secure file that is guaranteed to be cleaned up (sans a kill -9) is acceptable to you. I use this Bash helper function to wrap over ssh, scp, and sftp allowing me to pass the key as the first argument:

with-ssh-key() (
    # Bash wrapper for OpenSSH CLI tools (ssh, scp, sftp, or any that accept
    # the `-i /path/to/keyfile` option) that allows for passing an SSH private
    # key as the first parameter rather than via a persistent identity file.
    # This is something that is not possible with ssh tools by default.
    # This command is useful when storing the key in an environment variable
    # that is set in some secure way, such as set via secrets mastered in the
    # AWS Parameter Store, or as the result another command in a subshell.
    # The "OPENSSH PRIVATE KEY" lines at the beginning and end of the passed
    # key string will be added if they are not on already present.
    # Usage: with-ssh-key {ssh | scp | sftp} KEY ADDITIONAL_ARGS...
    # Examples:
    #   key="b3blbnnzaC1rZXktdjEAAAAABG5vbmUAAAA[etc...]"
    #   with-ssh-key "$key" ssh myserver echo Hello-from-myserver
    #   with-ssh-key "$key" scp myserver:file.txt ./
    #   echo get file.txt | with-ssh-key "$key" sftp myserver
    # A note regarding zsh: this wrapper is not necessary in zsh because zsh
    # has the extremely convenient `=(cmd)` syntax which will create a real
    # on-disk tempfile and clean it up immediately after the calling command
    # exits. Thus you can easily do something like this in zsh to accomplish
    # the same effect as this wrapper:
    #   ssh -i =(echo "$key") ...
    # Or, if you need to add the header/footer lines:
    #   ssh -i =(echo "-----BEGIN OPENSSH PRIVATE KEY-----\n$(echo "$key")\n-----END OPENSSH PRIVATE KEY-----")
    set -euo pipefail
    key="$1"; shift
    cmd="$1"; shift
    chmod 600 "$tempkeyfile" # ensure file is locked down
    trap "rm \"$tempkeyfile\"" 0 2 3 15 # ensure tempfile cleanup in event of various signals
    [[ $key =~ "BEGIN OPENSSH PRIVATE KEY" ]] || echo "-----BEGIN OPENSSH PRIVATE KEY-----" >> $tempkeyfile
    echo "$key" >> $tempkeyfile
    [[ $key =~ "END OPENSSH PRIVATE KEY" ]] || echo "-----END OPENSSH PRIVATE KEY-----" >> $tempkeyfile
    $cmd -i "$tempkeyfile" "$@"
  • (1) You should say "$cmd" (including the quotes) in the last line.  (2) Why would you ever need to say echo "thing₁\n$(echo "thing₂")\n-----thing₃"? Commented Jan 22, 2022 at 1:01

You must log in to answer this question.

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