Creating a Permanent SSH Tunnel Between Linux Servers

Posted on May 28th, 2008 in General,Linux System Administration,MySQL,Security by Brandon

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 Responses to 'Creating a Permanent SSH Tunnel Between Linux Servers'

Subscribe to comments with RSS or TrackBack to 'Creating a Permanent SSH Tunnel Between Linux Servers'.

  1. Brandon said,

    on June 6th, 2008 at 4:47 pm

    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. Anonymouse said,

    on August 20th, 2008 at 7:39 am

    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. on October 10th, 2008 at 5:07 pm

    [...] and tricks to creating a persistent ssh tunnel.  I came across several methods but liked the one proposed by Brandon Checketts the best.  You should, of course, create an unprivileged user (named tunnel perhaps) on both [...]

  4. Matt said,

    on March 11th, 2009 at 6:37 pm

    I run multiple ssh tunnels between our internal servers and cloud-based (e.g. EC2) servers and have found autossh to be the cat’s meow. Check it out http://www.harding.motd.ca/autossh/

  5. kNo said,

    on March 12th, 2009 at 7:03 am

    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

  6. PermaSSH said,

    on July 6th, 2009 at 6:09 am

    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” ???

  7. adhit said,

    on August 6th, 2009 at 4:18 am

    Heya,

    Thanks for the information :)

    It really works for me ..

  8. John said,

    on January 15th, 2010 at 3:39 pm

    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

  9. kat said,

    on January 20th, 2010 at 7:04 pm

    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?

  10. Sidailurch said,

    on May 5th, 2010 at 11:20 pm

    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

  11. Kurt Kraut said,

    on June 3rd, 2010 at 11:13 am

    SSH can do the job, but OpenVPN is really the best option for a permanent tunnel. It is more resilient than autossh.


  12. on August 24th, 2010 at 3:06 pm

    [...] reading: – http://www.howtoforge.com/mysql_master_master_replicationhttp://www.brandonchecketts.com/archives/creating-a-permanent-ssh-tunnel-between-linux-servers [...]

  13. dave said,

    on February 24th, 2011 at 9:19 pm

    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.

  14. uday said,

    on February 13th, 2012 at 3:35 am

    it is working perfect … Thanks a lot .. it saved a lot of time ….

  15. chris said,

    on February 22nd, 2012 at 11:11 am

    FYI…If you are setting up a tunnel to allow other users to access you need to include the -g option

  16. Simon said,

    on March 13th, 2012 at 3:06 pm

    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.

Post a comment

Please copy the string aaSwkB to the field below: