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
fiSave 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.
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.
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.
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 [...]
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/
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
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” ???
on August 6th, 2009 at 4:18 am
Heya,
Thanks for the information
It really works for me ..
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
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?