flowchart TB
subgraph T1["Terminal 1: Interactive"]
direction LR
A1["💻 Laptop"] -->|"ssh"| B1["Login Node"]
B1 -->|"salloc / interactive"| C1["⚙ Worker Node"]
end
subgraph T2["Terminal 2: SSH tunnel"]
direction LR
A2["💻 Laptop"] -->|"ssh -L port:node:port"| B2["Login Node"]
end
T1 ~~~ T2
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/xauthIn 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_ed25519User 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.seX11 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 yesIt’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 yesProxy 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 rackhamHost dardel
User dardel_username
Hostname dardel.pdc.kth.se
ForwardAgent yes
ProxyJump rackham
IdentityFile ~/.ssh/id_ed25519Multiple 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 both examples below, you will need two terminal sessions:
- Terminal 1 runs your interactive session on a worker node — this is where you start your server.
- Terminal 2 sets up the SSH tunnel from your laptop to the login node (not to the worker node) — you don’t run anything here.
In this example, we’ll:
- Connect from our Laptop to Dardel and get an allocation (Terminal 1).
- SSH into the allocated worker node (Terminal 1).
- Set up port forwarding from our Laptop through Dardel’s login node (Terminal 2).
- Start Marimo in headless mode on the worker node (Terminal 1).
- Open the connection in our browser.
Request an interactive allocation
In Terminal 1, connect to Dardel:
ssh dardelThen 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 sharedNote the node name from the output, then SSH into it:
ssh $SLURM_NODELISTSLURM_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.
The salloc command in Terminal 1 holds the allocation. If you log out of Terminal 1 or the connection drops, the allocation is released and the worker node becomes unavailable. Keep this session open for the entire duration of your work.
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 (Terminal 2) on your local machine:
ssh -L 8080:<node_id>:8080 dardelWhere -L enables port forwarding, the first 8080 is the local port, and <node_id>:8080 is the host:hostport. If you only want to forward from the login node (e.g., for a service running there), use -L 8080:localhost:8080 instead.
After running this command, you will be on the login node, not the worker node. This is expected — the login node acts as a relay for the tunnel. Do not run your server here; that belongs in Terminal 1 on the worker node.
If the node ID were known in advance, both the interactive session and the port forwarding could be set up in a single terminal. In practice, since the node is assigned dynamically, two terminals are needed.
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
Back in Terminal 1 (your interactive session on the worker node), let’s make an environment using Pixi.
curl -fsSL https://pixi.sh/install.sh | shMake 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 python marimo
pixi shell
marimo edit --headless --host 0.0.0.0 --port 8080We use --headless so marimo doesn’t try to open a browser on the worker node. The --host listens on all interfaces, and --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
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
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.
In this example, we’ll:
- Connect from our Laptop to Pelle and request an interactive allocation (Terminal 1).
- Set up port forwarding from our Laptop through Pelle’s login node (Terminal 2).
- Start Marimo in headless mode on the compute node (Terminal 1).
- Open the connection in our browser.
Request an interactive allocation
In Terminal 1, connect to Pelle:
ssh pelleThen request an interactive allocation. Unlike Dardel, the interactive command automatically places you on the allocated compute node — no manual ssh to the node is needed.
interactive -A <project_id> -n 1 -t 1:00:00Once resources are allocated, note the node name from the output, e.g.:
salloc: Nodes p115 are ready for job
Direct SSH to worker nodes is not permitted on Pelle. The interactive command handles this automatically, placing you directly on the compute node once resources are allocated.
Enable SSH tunneling
In a new terminal (Terminal 2) on your local machine, set up port forwarding using the node name from the previous step:
ssh -L 8080:<node_id>:8080 pelleAfter running this command, you will be on the login node, not the compute node. This is expected — the login node acts as a relay for the tunnel. Do not run your server here; that belongs in Terminal 1 on the compute node.
If the node ID were known in advance, both the interactive session and the port forwarding could be set up in a single terminal. In practice, since the node is assigned dynamically, two terminals are needed.
It’s easy to accidentally run commands in the wrong location. Use your prompt to identify where you are, e.g. username@pelle1 means you’re on the login node, and username@p115 means you’re on the allocated worker node.
Run Marimo using Pixi
Back in Terminal 1 (your interactive session on the compute node), set up an environment using Pixi.
curl -fsSL https://pixi.sh/install.sh | shMake 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 python marimo
pixi shell
marimo edit --headless --host 0.0.0.0 --port 8080We use --headless so marimo doesn’t try to open a browser on the worker node. The --host listens on all interfaces, and --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=sp176ZLLI1vyzjm9HNWfeA
➜ Network: http://172.18.202.115:8080?access_token=sp176ZLLI1vyzjm9HNWfeA
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
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 moAdd 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 + Cto shutdown the marimo webserver. - Use
exitto exit the Pixi shell. - Use
exitto exit the worker allocation. - Use
exitto 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