Creating a Permanent SSH Tunnel Between Linux Servers

I recently had a need to create a permanent SSH tunnel between Linux servers. My need was to allow regular non-encrypted MySQL connections over an encrypted tunnel, but there could be many other uses as well. Google can identify plenty of resources regarding the fundamental SSH commands for port forwarding but I didn’t ever find a good resource for setting up a connection and ensuring that it remains active, which is what I hope to provide here.

The SSH commands for port forwarding can be found in the ssh man page. The steps described here will create an unprivileged user named ‘tunnel’ on each server. That user will then be used to create the tunnel and run a script via cron to ensure that it remains up.

First, select one of the servers that will initiate the SSH connection. SSH allows you to map both local and remote ports, so it doesn’t really matter which end of the connection you choose to initiate the connection. I’ll refer to the box that initiates the connection as Host A, and the box that we connect to as Host B.

Create a ‘tunnel’ user on Host A:

[root@hosta ~]# useradd -d /home/tunnel tunnel
[root@hosta ~]# passwd tunnel       ## Set a strong password
[root@hosta ~]# su - tunnel           ## Become the user 'tunnel'

Now create a public/private key pair:

[tunnel@hosta ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/tunnel/.ssh/id_rsa):    ## hit enter to accept the default
Enter passphrase (empty for no passphrase):                           ## don't use a  passphrase
Enter same passphrase again:
Your identification has been saved in /home/tunnel/.ssh/id_rsa.
Your public key has been saved in /home/tunnel/.ssh/id_rsa.pub.
The key fingerprint is:
6f:30:b8:e1:36:49:74:b9:32:68:6e:bf:3e:62:d3:c2 tunnel@hosta

Now cat out the id_rsa.pub file which contains the public key that we will need to put on host b:

[tunnel@hosta ~]# cat /.ssh/id_rsa.pub
ssh-rsa blahAAAAB3NzaC1yc2EAAAABIwAAAQEA......6BEKKCxTIxgBqjLP tunnel@hosta

Now create a ‘tunnel’ user on Host B and save the public key for tunnel@hosta in the authorized_keys file

[root@hostb ~]# useradd -d /home/tunnel tunnel
[root@hostb ~]# passwd tunnel       ## Set a strong password
[root@hostb ~]# su - tunnel
[tunnel@hostb ~]# mkdir .ssh
[tunnel@hostb ~]# vi .ssh/authorized_keys   ## Now paste in the public key for tunnel@hosta

At this point you should be able to ssh from tunnel@hosta to tunnel@hostb without using a password. Depending on your configuration, you might need to allow the user ‘tunnel’ in /etc/ssh/sshd_config. You might also set some SSH options like the destination port in ~/.ssh/config.

Now, create this script as hosta:/home/tunnel/check_ssh_tunnel.sh

createTunnel() {
    /usr/bin/ssh -f -N -L13306:hostb:3306 -L19922:hostb:22 tunnel@hostb
    if [[ $? -eq 0 ]]; then
        echo Tunnel to hostb created successfully
    else
        echo An error occurred creating a tunnel to hostb RC was $?
    fi
}
## Run the 'ls' command remotely.  If it returns non-zero, then create a new connection
/usr/bin/ssh -p 19922 tunnel@localhost ls
if [[ $? -ne 0 ]]; then
    echo Creating new tunnel connection
    createTunnel
fi

Save that file and make it executable:

chmod 700 ~/check_ssh_tunnel.sh

This script will attempt to SSH to localhost port 19922 and run the ‘ls’ command. If that fails, it will attempt to create the SSH tunnel. The command to create the SSH tunnel will tunnel local port 13306 to port 3306 on hostb. You should modify that as necessary for your configuration. It will also create a tunnel for local port 19922 to port 22 on hostb which the script uses for testing the connection.

Now just add that script to the user ‘tunnel’s crontab to check every few minutes, and it will automatically create a tunnel and reconnect it if something fails. When it does create a new connection it will send an email to the ‘tunnel’ user, so you can create a .forward file to forward those messages to you.

16 thoughts on “Creating a Permanent SSH Tunnel Between Linux Servers”

  1. FWIW, I had the network connection interrupted for a bit this morning, and the tunnel came back up perfectly once the network was back up. The MySQL replication going over it also worked flawlessly, re-establishing the connection and picking up where it left off.

    I love it when things work like they should.

  2. Thanks for this! I could have made this work, I’m sure, but it’s always nice to find the solution you’re looking for as the first result on Google.

  3. Thanks for the code, but there is a mistake:
    /usr/bin/ssh -f -N -L13306:hostb:3306 -L19922:hostb:22 tunnel@hostb
    sould be
    /usr/bin/ssh -f -N -L13306:hosta:3306 -L19922:hosta:22 tunnel@hostb
    or /usr/bin/ssh -f -N -L13306:localhost:3306 -L19922:localhost:22 tunnel@hostb
    or
    /usr/bin/ssh -f -N -L13306:127.0.0.1:3306 -L19922:127.0.0.1:22 tunnel@hostb

    Greets from spain

  4. Thanks for this workaround, but i think kNo has right with his comment or i´m not understand the use of local forwarding.

    Another issue that´s confusing me is that u don´t use the -i option in your ssh-commands:
    /usr/bin/ssh -p 19922 tunnel@localhost ls
    AND
    /usr/bin/ssh -f -N -L13306:hostb:3306 -L19922:hostb:22 tunnel@hostb
    Shouldn´t it be for example: “/usr/bin/ssh -p 19922 – i /home/tunnel/.ssh/id_rsa tunnel@localhost ls” ???

  5. I did some slight modification to the checking script, to get rid of the 2 port requirement, and to make it command line parameter driven.

    #!/bin/bash

    tunnel_entrance_port=$1
    tunnel_end=$2
    destination=$3
    destination_port=$4

    ## use nc to connect to tunnel entrance port. If it returns non-zero, then create a new connection
    nc -w1 localhost $tunnel_entrance_port
    if [[ $? -ne 0 ]]; then
    echo Creating new tunnel connection
    /usr/bin/ssh -f -N -L $tunnel_entrance_port:$destination:$destination_port $tunnel_end
    if [[ $? -eq 0 ]]; then
    echo Tunnel to $destination through $tunnel_end created successfully
    else
    echo An error occurred creating a tunnel to $destination through $tunnel_end was $?
    fi
    fi

  6. I’m still looking for help on related issue….This is scenario:

    I’m on my laptop, I want to run GUI that is on serverB.
    I do not have direct access to serverB. I do, however, have access to serverA and from there can get to Server B.

    For comman line stuff…I use putty to get to ServerA, and ssh to ServerB. How do I set up tunnel to be able to use VNCclient on my laptop to run vncserver on ServerB.

    I know how to create putty tunnel to serverA in order to run vncserver on serverA. But how to do it when I have to get to ServerB thru serverA.

    Anyone?

  7. kat – don’t know if you’re still looking, seeing as how i just stumbled upon this post – but i *think* what you’re looking for is x11vnc – that should do what you want. Hope it helps

  8. I’m running this on a router. It forwards ports 139 and 445 from a remote samba server. Local windows clients can now access remote network shares by pointing at the router.

    Had some problems with the original and the check-script posted by John on 15 Jan 2010. Got a fix that worked, though:
    Replaced:

    nc -w1 localhost $tunnel_entrance_port
    with:
    lsof -i :$tunnel_entrance_port

    The first passed an error every time, so the original script always tried to re-create the tunnel(s). lsof -i checks for a process listening to a particular port. I figured that if the port is already open, ssh won’t be able to create the tunnel anyway.

  9. I’ve modified this a bit for my usage and though some of you might find the info useful. I have a server at work with a static ip behind a firewall that I can’t change. I use that server to connect to another server (ssh port is only open to the work servers ip address) I can rdp into a machine on the same network and use putty to access the server.

    So what I need is a tunnel to my machine at home (I use a Dynamic DNS to ensure that my home network is accessible from anywhere) now at home I can ssh -p 2222 localhost and get into the server at work.

    Here’s my setup for you:

    createTunnel() {
    /usr/bin/ssh -f -N -R 2222:localhost:22 tunnel@[myhomenetworkname]
    if [[ $? -eq 0 ]]; then
    echo Tunnel to [myhomenetworkname] created successfully
    else
    echo An error occurred creating a tunnel to [myhomenetworkname] RC was $?
    fi
    }
    ## check to see if the tunnel is running. If it returns non-zero, then create a new connection
    ps x | grep “2222\:localhost\:22”
    if [[ $? -ne 0 ]]; then
    echo Creating new tunnel connection
    createTunnel
    fi

    All the rest of the instructions are the same.

Leave a Reply

Your email address will not be published. Required fields are marked *