skip to content

Vpn-less Persistent Ssh Sessions

For a long time, my daily workflow has been slowed down by having to reopen my SSH connections when transitioning from workplace to home and reciprocally: once network connection drops, every process launched from the starting shell just terminates.

Things started to improve when I realized I could stay inside tmux. Much like GNU screen, tmux is a terminal multiplexer that allows the user to detach and reattach terminal sessions. When network connection is interrupted, everything keeps running inside the multiplexer.

From that point, losing network when leaving the office meant I would have to reconnect to my servers once arrived at home, then reattach to still running tmux sessions before being operational again.

Better. Yet not transparent enough.

The ideal solution should achieve:

  • close the laptop lid
  • commute
  • open the laptop lid; nothing to do, everything is still there

The requirements above mean two things:

  • being able to connect to any work server from everywhere, by hostname
  • automatically reattach to tmux sessions when network goes up again

Vpn Less SSH Sessions

To reach any work machine from home without a VPN tunnel, you need a bastion host at your workplace. Configure it to accept SSH connections coming from the WAN interface (that is, the internet). For maximum security, only allow public-key authentication.

We will refer to this bastion host as
Workstations we want to reach are called mustafar, kamino, and tatooine. These don’t need to be visible from outside of the company LAN (that’s the whole point of the article).

The idea is the following: when connecting to one of those workstations, always tunnel everything through the bastion host.

This is achieved with the help of OpenSSH 5.4 ‘netcat mode’ (ssh -W) in conjunction with a ProxyCommand directive.

In your ~/.ssh/config, place the following lines:

Host bastion

  ProxyCommand none
  Compression yes

Host mustafar kamino tatooine
  ProxyCommand ssh bastion -W %h:%p

By now, every time you do:

$ ssh kamino

The connection will automatigically be tunneled through the bastion host.
Obviously, the bastion host must also be reachable with its public hostname from inside your company LAN.

You can even make this configuration a bit more generic by adding:

Host *
  ProxyCommand ssh bastion -W `echo %h |sed s/.\somewhereinspace\.com//g`:%p

Persistent Connections

To achieve persistent SSH connections, we will use both OpenSSH 5.6 ControlPersist directive and autossh.

At the end of your ~/.ssh/config, place the following lines:

Host *
  ControlMaster auto
  ControlPersist 1h
  ControlPath /tmp/ssh_mux_%h_%p_%r
  ServerAliveInterval 30
  ServerAliveCountMax 4

ControlMaster and ControlPersist directives tell OpenSSH to share multiple sessions over a single master connection. The master connection will remain open 1 hour after the initial client connection has been closed.

ServerAliveInterval and ServerAliveCountMax control how OpenSSH decides to disconnect and terminate the session when the remote server does not answer server alive messages.

autossh is the last piece of our puzzle: autossh is a program to start a copy of ssh and monitor it, restarting it as necessary should it die or stop passing traffic.

We use autossh along with tmux to automagically restart closed SSH connections and reattach to tmux sessions.

Place the following shell function in your ~/.bash_profile or ~/.bashrc:

function rtmux {
  case "$2" in
    "") autossh -M 0 $1 -t "if tmux -qu has; then tmux -qu attach; else EDITOR=vim tmux -qu new; fi";;
    *) autossh -M 0 $1 -t "if tmux -qu has -t $2; then tmux -qu attach -t $2; else EDITOR=vim tmux -qu new -s $2; fi";;

We disable autossh monitoring with -M 0 because ServerAliveInterval and ServerAliveCountMax OpenSSH directives already control when the ssh client exits.

The “EDITOR=vim” bits are here to instruct tmux we want Vi like key-bindings. You can drop that part.

To persistently reattach to the current tmux session on kamino, we do:

$ rtmux kamino

If we want to use a specific tmux session, we do:

$ rtmux kamino <tmux session name>