Accessing a machine from any network through SSH

#SSH

šŸ“…Ā  01 Jun 2020 | ā˜•ļøĀ  6 min read

Imagine you've just wrapped up prototyping a new DL Model on your trusty Laptop at home. To actually train it, however, you need to access a powerful Desktop machine running within the secure confines of your university's private network. Unfortunately, without a way to connect to that machine, you're left waiting until you can physically get to the lab to start training your model. This naturally leads to the question: what if you could access the machine remotely? Wouldn't that be much more convenient? Likewise, we can imagine the need for remote access being relevant on a larger scale since most of our home/office machines also lack a public IP address to access them via the Internet. So, what options do we have for setting up such a remote access system?

Assuming the target desktop environment has a compatible display server, the simplest option is to use GUI-based softwares like Anydesk or TeamViewer. They are very easy to set up and work great for a variety of use cases. However, they have their quirks, especially when it comes to long interactive coding sessions. Under free license usage, you are likely to experience frequent timeouts and extremely laggy connections due to their overcrowded public servers.

Here's where a dedicated SSH connection comes in to save the day. It is the go-to solution when dealing with these terminal-heavy tasks where GUI access isn't required or simply isn't available (e.g., a server). Not only that, SSH also allows you to run GUI applications remotely with X11 forwarding. Similar to the GUI products mentioned before, services such as Ngrok exist to streamline setting up an SSH connection in a scenario like the above. However, in this post, we'll see how to build the same functionality by leveraging a public Relay Server. On top of being much more cost-efficient, this setup is particularly helpful to manage access when working in a team, where multiple users would share the computing resources of the same machine concurrently.

Requirement

To establish a connection to our target machine, we'll require another machine with a static public IP address, which will act as our Relay Server. Typically, we can rent a public IP machine from our local ISP, but in case that's not an option, cloud services like AWS and GCP also provide VMs with public IP addresses under their free tier usage. For the sake of simplicity, we'll assume we have access to such a machine and all machines involved in the connection use a Debian-based OS.

Approach

Our connection will be established as follows:

diagram

  • Step 1: The Target Machine forwards its SSH port to the Relay Server using a Reverse SSH Tunnel.
  • Step 2: When initiating a new SSH connection, the Source Machine (e.g. the Laptop in our scenario) will connect to the Relay Server.
  • Step 3: The Relay Server will forward that connection to the Target Machine.

Configuration

To enable future SSH connections, we'll need to configure the machines in the order shown below. Note that these steps need to be done only once.

Configuring the Relay Server

Step 1: Set up an Openssh server.

sudo apt-get update
sudo apt install openssh-server
sudo systemctl enable ssh
sudo ufw allow ssh
sudo systemctl start ssh

Configuring the Target Machine

Step 1: Install the dependencies and set up an Openssh Server.

sudo apt-get update
sudo apt install openssh-server openssh-client pwgen net-tools
sudo systemctl enable ssh
sudo ufw allow ssh
sudo systemctl start ssh
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa > /dev/null
echo ListenAddress 127.0.0.1 | sudo tee -a /etc/ssh/sshd_config

Step 2: Define necessary environment variables. You need to replace <RELAY_USERNAME> and <RELAY_IP> with appropriate values. The LOCAL_PORT variable can be set to any unused port.

export RELAY_USERNAME=<RELAY_USERNAME>
export RELAY_IP=<RELAY_IP>
export LOCAL_ADDR=127.0.0.1
export LOCAL_PORT=33322
export REMOTE_PORT=22
export REMOTE_HOST=127.0.0.1

Step 3: Copy the public key to the Relay Server.

cat ${HOME}/.ssh/id_rsa.pub | ssh ${RELAY_USERNAME}@${RELAY_IP} "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

Step 4: Create a Systemd service to start the SSH tunnel. This service starts the tunnel on each boot, saving us the trouble of restarting the tunnel manually each time the Target Machine restarts.

echo "[Unit]
Description=Setup a secure tunnel to ${RELAY_USERNAME}
After=network.target

[Service]
StandardOutput=file:/tmp/secure-tunnel-${RELAY_USERNAME}.log
ExecStart=/usr/bin/ssh -i ~/.ssh/id_rsa -v -N -T -o ServerAliveInterval=60 -o ServerAliveCountMax=10 -o StrictHostKeyChecking=no -R ${LOCAL_ADDR}:${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} ${RELAY_USERNAME}@${RELAY_IP}
RestartSec=10
Restart=on-failure

[Install]
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/secure-tunnel-${RELAY_USERNAME}.service

sudo systemctl enable secure-tunnel-${RELAY_USERNAME}.service
sudo systemctl start secure-tunnel-${RELAY_USERNAME}.service

sudo systemctl status secure-tunnel-${RELAY_USERNAME}.service

If everything went right, the final command will yield something like the following in the terminal:

ā— secure-tunnel-sample.service - Setup a secure tunnel to sample
   Loaded: loaded (/etc/systemd/system/secure-tunnel-sample.service; indirect; vendor preset: enabled)
   Active: active (running) since Sat 2020-03-01 23:03:08 +06; 1 weeks 0 days ago
 Main PID: 30981 (ssh)
    Tasks: 1 (limit: 4915)
   CGroup: /system.slice/system-secure\x2dtunnel.slice/secure-tunnel-sample.service
           ā””ā”€30981 /usr/bin/ssh -i ~/.ssh/id_rsa -i ~/.ssh/id_rsa-v -N -T -o ServerAliveInterval=60 -o ServerAliveCountMax=10 -o StrictHostKeyChecking=no -R 127.0.0.1:33322:127.0.0.1:2

Configuring the Source Machine

Step 1: Set up the OpenSSH client.

sudo apt-get update
sudo apt install openssh-client
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa > /dev/null

Step 2: Define necessary environment variables (replace the placeholder strings with <> accordingly).

export RELAY_USERNAME=<RELAY_USERNAME>
export RELAY_IP=<RELAY_IP>
export TARGET_USERNAME=<TARGET_USERNAME>
export TARGET_PORT=33322

Step 3: Edit the SSH config.

echo "

Host Relay
  User ${RELAY_USERNAME}
  HostName ${RELAY_IP}

Host Target
  User ${TARGET_USERNAME}
  HostName 127.0.0.1
  Port ${TARGET_PORT}
  ProxyJump Relay" >> ~/.ssh/config

Step 3: Copy the public key to the Relay Server and Target Machine.

cat ~/.ssh/id_rsa.pub | ssh Relay "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"
cat ~/.ssh/id_rsa.pub | ssh Target "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

And we are done! Now we can use the following command to connect to the Target Machine.

ssh Target

Where To Next...

Now that SSH is set up, we can also use Visual Studio Code to interactively develop and execute our coding workloads directly on the Target Machine (tutorial).

ssh_demo

Image source: Microsoft

Ā© 2025 built with Next.js by Abhik Bhattacharjee