Introduction to SSH Config and Tunneling

SSH
Author

Mahesh

Published

April 15, 2025

Modified

April 29, 2025

This introduction will cover the basics of SSH config, explaining how it works and how you can use it to simplify your SSH connections. SSH config is a file that allows you to store settings for your SSH connections, providing a flexible system for defining and reusing connection parameters, and significantly simplifying server management.

The config file

The ssh program on a host receives its configuration from either the command line or from configuration files ~/.ssh/config and /etc/ssh/ssh_config 1.

Command-line options take precedence over configuration files. The user-specific configuration file ~/.ssh/config is used next. Finally, the global /etc/ssh/ssh_config file is used. The first obtained value for each configuration parameter will be used.

The ssh_config client configuration file has the following format. Both the global /etc/ssh/ssh_config and per-user ~/ssh/config have the same format.

  • Empty lines and lines starting with ‘#’ are comments.
  • Each line begins with a keyword, followed by argument(s).
  • Configuration options may be separated by whitespace or optional whitespace and exactly one =.
  • Arguments may be enclosed in double quotes (“) in order to specify arguments that contain spaces.

Example:

Host github.com
  AddKeysToAgent yes
  IdentityFile ~/.ssh/id_ed25519

Host rackham rackham1 rackham2 rackham3 rackham4
  User rackham_username
  Hostname %h.uppmax.uu.se
  IdentityFile ~/.ssh/id_ed25519
  ForwardAgent yes
  # ForwardX11 yes

Host nac
  User nac_username
  Hostname nac-login.nbis.se
  ForwardAgent yes
  IdentityFile ~/.ssh/id_ed25519

Host dardel
  User dardel_username
  Hostname dardel.pdc.kth.se
  ForwardAgent yes
  IdentityFile ~/.ssh/id_ed25519

# XAuthLocation added by XQuartz (https://www.xquartz.org)
Host *
  XAuthLocation /opt/X11/bin/xauth

In your config you’ll find blocks that start with Host <label>. When the Hostname is not supplied, the label provides the default hostname. AddKeysToAgent yes tells your SSH client to automatically add the private key supplied by IdentityFile.

Common configuration options

The identity file

This supplies the path to the SSH key file that should be used for authenticating when connecting to a host. There are several types of keys, and the example below uses an EdDSA key which is the default 2. Github has a very nice guide to generating keys 3.

IdentityFile ~/.ssh/id_ed25519

User and Hostname

If your user and hostname is not the same as your local user you can also supply your username when connecting to the host

Host nac
  User nac_username
  Hostname nac-login.nbis.se

X11 Forwarding

X11 forwarding (ssh -X) is a technique that allows you to run graphical applications on a remote server but display them on your local machine. This can however be slow. An alternative is port forwarding (see below).

ForwardX11 yes
Tip

It’s not recommended to enable this by default as it might start the X Server locally each time you log in, which could be a significant delay

Agent forwarding

Agent forwarding (ssh -A) in SSH allows you to use your local SSH keys to authenticate to further servers that the server you’ve connected to needs to access, without having to copy your private keys to that intermediate server.

ForwardAgent yes

Proxy Jump

You can use SSH to automatically connect to one host via another. This can be on demand with -J <hostname>, or it can be added permanently to a host using ProxyJump.

ssh dardel -J rackham
Host dardel
  User dardel_username
  Hostname dardel.pdc.kth.se
  ForwardAgent yes
  ProxyJump rackham
  IdentityFile ~/.ssh/id_ed25519

Multiple proxies can be chained in this way.

SSH tunneling

Port forwarding

Port forwarding, also known as SSH tunneling, is a technique that allows you to redirect network traffic through an SSH connection. For example, you can access remote databases, connect to Jupyter/Posit notebooks/servers, and forward web applications locally.

In this example, we’ll:

  1. Connect from our Laptop to Dardel.
  2. Get an allocation.
  3. Connect again from our Laptop to Dardel, but this time specify the port.
  4. Connect from Dardel to our allocation.
  5. Start Marimo in headless mode
  6. Open the connection on our browser.

Request an interactive allocation

Running interactive jobs on Dardel

First connect to dardel

ssh dardel

Then request an allocation

# Request 4 cpus because of the low memory per core allocation.
salloc -c 4 -t 1:00:00 -A naiss2024-22-1346 -p shared

Normally you would then

ssh $SLURM_NODELIST

and start with your command-line operations, but not just yet.

Tip

SLURM_NODELIST contains the list of nodes you just reserved. If you’ve reserved more than one node, then echo $SLURM_NODELIST followed by ssh <NODE_ID> of the node you want to ssh into.

Enable SSH tunneling

Decide the port you’re going to use to tunnel. We’ll use 8080, which is a standard in webserver testing, as we’ll be using Marimo which starts a web-service and runs in the browser.

In a new terminal on your local machine:

ssh -L 8080:<node_id>:8080 dardel
ssh <node_id>

Where -L enables port forwarding, the first 8080 is the local port, and the <node_id>:8080 is the host:hostport. If you only want to forward from the login node for example, then you might use -L 8080:localhost:8080 instead.

Caution

It’s easy to accidentally run commands in the wrong location. Use your prompt to identify where you are, e.g. username@login1 means you’re still on the login node, and username@node_id means you’re on the allocated worker node.

Run Marimo using Pixi

Once you’ve ssh’ed in, you’ll be put in your home directory. Let’s make an environment using Pixi.

curl -fsSL https://pixi.sh/install.sh | sh

Make a directory to start a Marimo server, and start one.

mkdir marimo_test
cd marimo_test
pixi init -c conda-forge -c bioconda
pixi add marimo
pixi shell
marimo edit --headless --host 0.0.0.0 --port 8080

We use --headless so marimo doesn’t try to open a browser on the worker node. The --host listens on all ports, and the --port is set to 8080.

When Marimo starts up, it will display a URL to copy-and-paste into your local browser.

$ marimo edit --headless --host 0.0.0.0 --port 8080

        Create or edit notebooks in your browser 📝

        ➜  URL: http://0.0.0.0:8080?access_token=kXxlpkz81MXl9fWF65VFqA
        ➜  Network: http://10.253.5.60:8080?access_token=kXxlpkz81MXl9fWF65VFqA
Tip

If you get an error message such as,

channel 4: open failed: connect failed: No route to host
channel 5: open failed: connect failed: No route to host
channel 4: open failed: connect failed: No route to host
channel 5: open failed: connect failed: No route to host

there may already be a service running on the port you chose, e.g. 8080. In this case, select another port and try again, for example, changing ports to 8081.

Quick play with Marimo

Create a new notebook, and then in the first cell add.

import marimo as mo

Add a new Python cell, and then add the following quick mermaid diagram.

diagram = '''
graph LR
    A --> B --> C
    A --> C
'''
mo.mermaid(diagram)

and run it.

Cleaning up

  • Use Ctrl + C to shutdown the marimo webserver.
  • Use exit to exit the Pixi shell.
  • Use exit to exit the worker allocation.
  • Use exit to exit the login node.

Permanently configure

To configure services permanently, one can add the LocalForward to their config.

Host nac
  User nac_username
  Hostname nac-login.nbis.se
  # Blobtools service on port 8001 on login node
  LocalForward 8001 localhost:8001

References

Footnotes

  1. https://www.ssh.com/academy/ssh/config↩︎

  2. https://learning.lpi.org/en/learning-materials/102-500/110/110.3/110.3_01/↩︎

  3. https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent↩︎