SSH Tunneling For Dummies

A brutally simple collection of reminders, notes and gotchas you can use the next time you are faced with the need to tunnel your SSH client across one or more hosts

Posted by Miguel Pereira on Tue 21 June 2016

Port forwarding using SSH

Publish a service/port from a remote system by using a bastion to proxy requests. Useful, for instance, if you want to access a remote RDS MySQL or PostgreSQL instance using locally installed tools...

$ ssh -i {private_key}  -N \ 
  -L {local_port}:{remote_host}:{remote_port} \
  {bastion_user}@{bastion_host} &

The breakdown

parameter description
-i is used provide an identity file (a private key) that will authenticate you against the bastion & the remote host
-N tells SSH not to execute any commands remotely (which is the meat of port-forwarding)
-L {local_port}:{remote_host}:{remote_port} we are essentially saying, "_hey, any requests that I make to localhost:{local_port}, send them to {remote_host}:{remote_port}"

The Example

Imagine you have an instance of Postgres running on Amazon RDS on a private subnet, accessible only via a bastion with hostname; the RDS instance host name is something like and Postgres itself is running on its deafault port: 5432; our command would look something like this:

$ ssh -i ~/.ssh/id_rsa -N \
  -L \ &

You could then do something like this to connect to it via psql by talking to your local hosts address on port 5432 --make sure you have no local services bind on port 5432 in the first place.

$ psql --host=localhost --port=5432 --dbname=postgres --username=postgres --password

Command forwarding

Run commands against remote hosts transparently through agent forwarding. Lets use this basic layout as our reference ssh tunneling example

Wouldn't it be great if you could run adhoc commands or tunnel into remote_host just as you would a local host? i.e.

ssh centos@remote_host hostname

Well, you can. Read on.

Making things happen

Update your local user's ~/.ssh/config to include the following --don't worry, we'll break down each directive in a second

Host {remote_host}
  ProxyCommand ssh -W %h:%p {bastion_host}
  IdentityFile ~/.ssh/{private_key}

Host {bastion_host}
  Hostname {bastion_host}
  User {bastion_user}
  IdentityFile ~/.ssh/{private_key}
  ForwardAgent yes
  ControlMaster auto
  ControlPath ~/.ssh/bastion-%r@%h:%p
  ControlPersist 5m

The breadkdown

parameter description
{remote_host} refers to the host that you need to reach
{bastion_host} refers to the host you will tunnel through to get to {remote_host}
{private_key} this is the key for which its public key counterpart was injected into the bastion & remote hosts' authorized_keys stores
{bastion_user} this is the identity of the (remote) user that holds the authorized_keys store we mentioned above
Host {remote_host} this is essentially saying, "hey, if you need to do anything with a host that matches the pattern {remote_host} do the stuff that follows"
ProxyCommand this helps us specify a command to run on our proxy, in this case our bastion server
-W %h:%p {bastion_host} this is the creative part: "_hey, anything that happens on the std I/O on {bastion_host} go ahead and send it to host %h on port %p_"
User {bastion_user} this represents the idetity of the user that we are connecting to on the bastion host
IdentityFile ~/.ssh/{private_key} the local location of the private key that matches the public key on the authorized_hosts on the bastion host
ForwardAgent yes this will forward the keys on the local agent to the remote hosts without having to leave the keys remotely
ControlMaster auto this allows ssh to determine which TCP connection/socket to use to maintain communication between the local system, the bastion host & the remote one
ControlPath ~/.ssh/bastion-%r@%h:%p specifies the location for the control socket used by the multiplexed sessions
ControlPersist 5m specifies that the control channel defined will persist for up to 5 minutes

Do you want more in-depth stuff on tunneling via SSH?

Comments !