A Known SSH Socket for Tmux Using a known, shared SSH socket to enable agent forwarding through an existing tmux session
Artisanal SSH socket remapping
I spend a lot of my development time working on a remote server, and regularly need to connect to a number of additional servers, all over SSH. Plus I often push and pull git-controlled code via SSH to yet more remote servers.
To keep track of everything, I do 100% of my work within tmux. To let me chain my SSH
connections, nearly every connection uses
Unfortunately, this doesn't work for long. When I reconnect to a
server and reattach my tmux session, I am suddenly unable to chain my
The problem here is that my SSH Agent has created a new socket for my new connection. This works fine by itself, but when I reattach the already existing tmux session, I no longer have any reference to the new socket. Inside of tmux, SSH will try to use the socket in use at the time the session was created, which probably no longer exists.
So what to do? The obvious solution is to simply close my tmux session when I disconnect and create a new one with every new connection. But this has problems.
- First, what if I accidentally disconnect? Maybe I've
lost my network connection, or somehow accidentally hit
~.. I want to get back into my session as quickly and easily as possible.
- Second, what if I want to save my panes when I disconnect? Maybe there's some long-running process I want to keep. Or maybe I simply don't want to have to recreate my session every time I connect (though some of this can be solved by a project like tmuxinator).
Obviously, a better solution would be to just fix the problem and get tmux to always use the current socket. Additionally, I want to be sure to support using tmux within SSH within tmux, chained arbitrarily. The answer is to always put the socket in a known location and hook everything up to use it.
Rather than try to devise some solution to signal to tmux what the current socket file is, it will be much easier to use a symbolic link. Whenever we create a new socket, we'll simply override the existing link with a link to the new socket.
We need a name for this symbolic socket, so how about
/tmp/ssh-agent-$USER-screen. We're putting it in
/tmp/ since it doesn't matter too much if this is
overwritten or cleaned up. We're also using the
environment variable to keep sockets separate for different users. At
the end, I'm putting
-screen since this is sort-of more
-tmux, but it can really be whatever, or
Now, creating a symbolic link is all fine and good, but what do we actually link to? Unfortunately there's no great built-in way to grab the current socket all the time. But there's no need to re-invent the wheel, we can use the proven ssh-find-agent tool. So let's put that in a useful location:
git clone email@example.com:wwalker/ssh-find-agent.git ~/lib/ssh-find-agent
We'll use the "automatic"
-a option, which will find
the active SSH agent and store it in
us. But, if there is no active SSH session, nothing useful will
happen, so we'll want to get the SSH agent started.
# Source the script first . ~/lib/ssh-find-agent/ssh-find-agent.sh ssh_find_agent -a # If nothing happened, we need to start up the ssh-agent if [ -z "$SSH_AUTH_SOCK" ] then eval $(ssh-agent) > /dev/null ssh-add -l >/dev/null || alias ssh='ssh-add -l >/dev/null || ssh-add && unalias ssh; ssh' fi
Now that we have the socket, we just need to make (or override) that symbolic link so it can be found later.
SOCK="/tmp/ssh-agent-$USER-screen" if test $SSH_AUTH_SOCK && [ $SSH_AUTH_SOCK != $SOCK ] then rm -f /tmp/ssh-agent-$USER-screen ln -sf $SSH_AUTH_SOCK $SOCK export SSH_AUTH_SOCK=$SOCK fi
Putting it all together, we'll find the active socket or create it,
then make a known symblic link. Now we just have to do this everywhere
the socket is needed. This is the most annoying part, though it can be
relieved with a tool like sshrc. The full
process will need to be added to the
~/.zshrc on your host system, as well as every system you
want to chain tmux and SSH sessions from.
To be clear about where this needs to happen, if your chain looks like this:
host → tmux → (remote 1) → tmux → (remote 2) → tmux → (remote 3) ↘ (remote 4) → tmux → (remote 5)
Then you would need to have this set-up on
(remote 2) and
4), but not the last two remotes. If you think of these chain
connections as a tree, the socket mapping is not needed on the leaves.
Technically it's also not needed on any nodes on which you're not
using tmux, provided you use
So there we have it, the SSH socket symbolically linked to a known location. After cloning ssh-find-agent, here's the complete script to add to your shell login script as required:
# Known SSH Socket for tmux # https://blog.jmthornton.net/p/tmux-known-socket . ~/lib/ssh-find-agent/ssh-find-agent.sh ssh_find_agent -a if [ -z "$SSH_AUTH_SOCK" ] then eval $(ssh-agent) > /dev/null ssh-add -l >/dev/null || alias ssh='ssh-add -l >/dev/null || ssh-add && unalias ssh; ssh' fi # Predictable SSH authentication socket location so tmux can find it SOCK="/tmp/ssh-agent-$USER-screen" if test $SSH_AUTH_SOCK && [ $SSH_AUTH_SOCK != $SOCK ] then rm -f /tmp/ssh-agent-$USER-screen ln -sf $SSH_AUTH_SOCK $SOCK export SSH_AUTH_SOCK=$SOCK fi