Streamlining SSH for remote work

With the university now working remotely, and our group working entirely on linux systems, I figured that now would be a good time to share some useful SSH commands to streamline remote access. This is far from an exhaustive list, but will hopefully serve as a useful starting point for anybody who finds themself needing to work remotely on a linux system.

SSH jump host

Suppose you want to connect to a remote machine that is only accessible via an intermediate host (e.g. a departmental gateway server). To streamline this process, and prevent mistakes when passing a set of options at each host we go through, we can instead use ProxyCommand to run ssh commands on intermediate hosts, allowing you to jump directly from your local machine to your destination with a single command.

Suppose we want to jump through an intermediate host, ‘intermediate’, to a remote host, ‘remote’. Instead of connecting to intermediate then connecting from intermediate to remote, we can run ssh with the -J, or ProxyJump, option:

ssh -J user@intermediate user@remote

If we need to go through multiple intermediate hosts, we can provide them all as a comma-separated list, and ProxyJump takes care of the rest:

ssh -J user@intermediate1,user@intermediate2 user@remote

Graphical applications

If you want to interact with graphical interfaces running remotely, you need to use X11 forwarding. Rather than forwarding the display of the remote system, this allows you to forward the display instructions for graphical applications, allowing you to interact with them locally. To enable X11 forwarding, simply run your SSH command with the -X option.

ssh -X user@host

However, X11 forwarding is slow, and requires a fast and stable network connection in order to run nice modern graphical applications smoothly, and don’t even think about running anything intense remotely.

Port forwarding

Often when working remotely, we need to interact with an application running on a particular port, for example a Jupyter session or Flask development server remotely. To do this, we need to connect to the remote machine, then redirect traffic from the port of interest on the remote machine to a port on our local machine. This is known as port forwarding, and is easy to set up using SSH.

As an example, suppose we want to launch a Jupyter session on our remote host, and interact with it locally. There are a few things we can do when we launch Jupyter to make things easier for ourselves. When starting a Jupyter session, you can optionally specify the port used by the Jupyter server. To do this, we use the –port option when starting Jupyter. We can also run Jupyter in the background, and disconnect it from our terminal session, allowing us to close our remote connection without killing the process. There are many ways to do this, one of which is using nohup. We can also suppress Jupyter’s output if we don’t want to kill the connection immediately, or pipe it to a log file if we want to check on it later.

nohup jupyter lab --no-browser --port=9000 > jupyter.log 2>&1 &

This starts a Jupyter lab session running on port 9000 as a background process, detaches it from the terminal, and redirects both stdout and stderr to the file jupyter.log. We’ve also told Jupyter not to launch a browser, as we’ll be connecting to the session using our local browser. We can do whatever we want with our terminal now, including killing the connection, without affecting Jupyter. We can always log in again later and kill Jupyter if we need to.

Now, let’s connect to Jupyter remotely. Jupyter sessions are accessible via localhost on the machine on which they are running. To connect to this session remotely, we forward a port on our local machine (port 8888 in this example) to localhost:9000 on ‘remote’ using the following SSH command:

ssh -L 8888:localhost:9000 user@remote

Now, ssh will forward any traffic on port 8888 of your local machine to port 9000 of the remote host. This means we can connect to anything running on localhost:9000 on remote via port 8888 on our local machine. Launching a web browser and going to localhost:8888 will connect you to the Jupyter session running remotely, allowing you to interact with it as if it were running locally.

Running ssh like this will start a terminal session on the remote host as normal. If you’re only interested in forwarding ports, and don’t want to keep a terminal alive on your local machine to maintain the connection, the -N and -f options are useful. -N tells ssh not to execute any commands on the remote host (such as starting a terminal session), and -f tells ssh to run in the background.

ssh -N -f -L 8888:localhost:9000 user@remote

You can combine port forwarding with host jumping if your session is running on a host behind one or more intermediates:

ssh -N -f -L 8888:localhost:9000 -J user@intermediate user@remote

The SSH config file and you

Running these commands every time you need to connect can get very tedious, very quickly – especially if you regularly need to access data on many different machines. Fortunately, there’s a straightforward solution to this problem: the SSH config file. Your per-user config file lives in ~/.ssh, so if it doesn’t already exist, create it now:

touch ~/.shh/config

Now we can store details for each of our hosts, and assign a memorable name to each. For example, a simple entry for our user account on our remote work desktop machine, mydesktop, might look like

Host desktop
    HostName mydesktop
    User user

Now, whenever we want to connect to our desktop, we simply run

ssh desktop

We can also add our intermediate hosts to the config file to simplify our host jumping:

Host middleman
    HostName intermediate
    User user

We can jump through both intermediates to our desktop by running

ssh -J middleman desktop

If our desktop is only accessible via an intermediate host, we can also include the ProxyJump command in our config file to streamline things even further:

Host jump-desktop
    HostName mydesktop
    ProxyJump intermediate
    User user

We can also save commonly-used port-forwarding configurations. For example, suppose we regularly run Jupyter sessions on port 9000 of our desktop. We can add the following entry to our config file:

Host tunnel-desktop
    HostName mydesktop
    ProxyJump intermediate
    LocalForward 8888 localhost:9000
    User user

which simplifies our port forwarding command to:

ssh -N -f tunnel-desktop

Wrapping up

SSH is a powerful tool that can do many, many things for you – far too much to cover in a simple blog post. Hopefully this has been a helpful introduction to SSH for those of you who are new to working remotely, or a handy refresher if you’ve done all of this before.

Author