469

Tunneling data over SSH is pretty straight-forward:

ssh -D9999 username@example.com

sets up port 9999 on your localhost as a tunnel to example.com, but I have a more specific need:

  • I am working locally on localhost
  • host1 is accessible to localhost
  • host2 only accepts connections from host1
  • I need to create a tunnel from localhost to host2

Effectively, I want to create a "multi-hop" SSH tunnel. How can I do this? Ideally, I'd like to do this without needing to be superuser on any of the machines.

Jeff Puckett
  • 544
  • 7
  • 20
Mala
  • 7,288
  • 11
  • 35
  • 38

18 Answers18

432

You basically have three possibilities:

  1. Tunnel from localhost to host1:

    ssh -L 9999:host2:1234 -N host1
    

    As noted above, the connection from host1 to host2 will not be secured.

  2. Tunnel from localhost to host1 and from host1 to host2:

    ssh -L 9999:localhost:9999 host1 ssh -L 9999:localhost:1234 -N host2
    

    This will open a tunnel from localhost to host1 and another tunnel from host1 to host2. However the port 9999 to host2:1234 can be used by anyone on host1. This may or may not be a problem.

  3. Tunnel from localhost to host1 and from localhost to host2:

    ssh -L 9998:host2:22 -N host1
    ssh -L 9999:localhost:1234 -N -p 9998 localhost
    

    This will open a tunnel from localhost to host1 through which the SSH service on host2 can be used. Then a second tunnel is opened from localhost to host2 through the first tunnel.

Normally, I'd go with option 1. If the connection from host1 to host2 needs to be secured, go with option 2. Option 3 is mainly useful to access a service on host2 that is only reachable from host2 itself.

Mika Fischer
  • 5,159
  • 2
  • 18
  • 15
  • 1
    I want to do browsing this way. Which one's best? I tried first one but it didn't work. I set a socks proxy in my browser localhost:1234 but no luck. :( please help.. – prongs Feb 29 '12 at 13:54
  • 2
    @prongs try option 3 – Mala May 03 '12 at 06:49
  • 1
    is there a way to forward my public key from localhost, through the tunnel of host 1, on to host2? – Noli Dec 19 '12 at 08:00
  • 7
    @Noli If you use ssh-agent (which you should), you can forward it through the connections using the `-A` option to ssh. – Mika Fischer Dec 19 '12 at 11:36
  • I have an application that (apparently) uses its own ssh client to connect over SFTP (PHPStorm). Can you explain whether these options will help me? I can't use authorized keys because of this and I only have a single password field. It's not going to prompt me. – Stephane Sep 17 '13 at 14:12
  • What means normally? The reason host1 exists is in most cases to securely enter host2. If you don't secure the host1-host2 connection you violate the security premises, or not? – erikbstack Aug 19 '15 at 09:52
  • Notes: Option `1` if you using `OSX` then remove `-N` option which doesn't work – Satish Jan 27 '16 at 13:50
  • 1
    Is it only me, but does option 2 leave one `ssh` listening on `host1` when one `Ctrl-C`s the local `ssh`? I am unable to execute the command twice in a row without going to `host1` and killing the last `ssh` process which is still listening o post `9999`. – musically_ut Nov 16 '16 at 06:20
  • 2
    @musically_ut - that's correct, the 2nd ssh command is backgrounded on host1, so if you want to reconnect locally to it, you only need to rerun the first part again. If you add `-f`, it will immediately background locally, thus `ssh -f -L 9999:localhost:9999 host1` will reconnect if you pressed ctrl-c in first case. Or add `-f` to the original double ssh command when first running it to background everything immediately. – Mark Fisher Feb 25 '17 at 15:10
  • When I tried option 3, and ran `ssh -L 9999:localhost:1234 -N -p 9998 localhost`, which password am I supposed to give? I tried both the passwords of the local and remote users, none worked. – Franck Dernoncourt Mar 18 '17 at 18:22
  • 1
    What exactly is insecure about option 1? Where is 'above'? – Xandaros Nov 07 '17 at 23:37
  • Hello, how can I extend this solution to another server? I'm trying to do the forwarding using vnc. Right now, I have a `localhost`, `guard`, `jump_host`, `target_server` and I want to port forward from `localhost` to `target_server`. Thanks! – zack Aug 31 '18 at 21:21
  • Thank you so much, the third option worked perfectly for me! :) – Yash Sodha Jun 03 '19 at 07:43
  • The `-N` parameter in the above command means that ssh won't execute a remote command, meaning that a shell won't be opened on the remote machine (`host1`) – kapad Mar 06 '20 at 10:41
  • I wanted to open the tunnels, start a browser, and have it clean up when the browser is closed. I used the 2nd option with `-t -t` parameters to make sure the 2nd tunnel closed when the process was killed. My full script (I had to use `-4` to ensure ipv4): `ssh -4 -t -t -L 9999:localhost:9999 host1 ssh -4 -L 9999:localhost:1234 -N host2 &` `google-chrome "https://localhost:9999"` `kill -9 $!` – Ruzihm Jan 10 '22 at 22:02
199

There is an excellent answer explaining the use of the ProxyCommand configuration directive for SSH:

Add this to your ~/.ssh/config (see man 5 ssh_config for details):

Host host2
  ProxyCommand ssh host1 -W %h:%p

Then ssh host2 will automatically tunnel through host1 (also works with X11 forwarding etc.).

This also works for an entire class of hosts e.g. identified by domain:

Host *.mycompany.com
  ProxyCommand ssh gateway.mycompany.com -W %h:%p

Update

OpenSSH 7.3 introduces a ProxyJump directive, simplifying the first example to

Host host2
  ProxyJump host1
kynan
  • 3,454
  • 2
  • 23
  • 17
  • 3
    Is there a way to do this conditionally? I only want to do this sometimes. Also, this is specifically for commands, but I'm looking for something for all of port 22 (ssh, sftp, etc). – Stephane Sep 11 '13 at 15:08
  • 1
    @Stephane what do you mean by *specifically for commands*? Your SSH config is used by anything using `ssh`, including `git`, `sftp` etc. afaik. – kynan Sep 11 '13 at 20:58
  • 1
    @Stephane I'm not aware of a way of enabling this conditionally (e.g. only when you're outside the network of the target host). I set this option for all hosts in question in a configuration block and then (un)comment the line as needed. Not perfect, but it works. – kynan Sep 11 '13 at 20:58
  • Hmmm can I cause ssh to use a different config file if I want it to? Thanks for these replies! – Stephane Sep 12 '13 at 00:46
  • 2
    @Stephane sure: `ssh -F /path/to/altconfig`. Beware this will ignore the system wide `/etc/ssh/ssh_config`. – kynan Sep 12 '13 at 11:39
  • 22
    An easy way to make settings "conditional" is to define two different hosts in .ssh/config, that have the same HostName. Connect to host2-tunnel when you want the tunnel, and host2 when you don't. – Steve Bennett Sep 23 '13 at 06:55
  • This is awesome answer !! good job ! – Meabed Apr 06 '14 at 23:08
  • Has anyone had any luck making this work using the openssh for windows distro here: http://www.mls-software.com/opensshd.html? It seems like I need to give it a full cygwin path to ssh but I attempted using the following andstill had no luck: ProxyCommand "/cygdrive/c/Program Files/OpenSSH/bin/ssh" ... – mockobject Jan 16 '15 at 18:39
  • ProxyJump is amazing! – Aaron McMillin Jun 04 '17 at 03:25
  • 1
    Easier than using `ProxyJump` is `-J` switch. – Martin Prikryl Apr 16 '18 at 07:29
  • @MartinPrikryl Depends on your use case: for one-off use `-J` is certainly easier than modifying your SSH config. However if you *always* need the proxy to access a certain host I find it easier to only change SSH config once. – kynan Aug 05 '18 at 11:22
  • Is there an elegant way to solve this if `host1`'s public key is encrypted? (elegant = without having remove the encryption?) – Alois Mahdal Jan 17 '19 at 10:19
  • @AloisMahdal Why would the host key be encrypted? It's a public key for a reason. – kynan Jan 20 '19 at 16:36
  • @kynan, my bad, I meant *private* key there. – Alois Mahdal Jan 20 '19 at 17:21
  • @AloisMahdal I don't understand how that is related. *Your* SSH client needs to be able to read *your*, the host's SSHD needs to be able to read the host's private key - irrespective of using `ProxyJump` or not. – kynan Jan 27 '19 at 15:32
  • @kynan I'm at `host0`, and want to connect to `host2`, which IP-wise will only talk to `host1`. I can already do `ssh host1`, and from the interactive shell just do `ssh host2`, answer the passphrase, and I'm there. Now: if I say `ssh host1 -J host2` it fails because it won't forward the password prompt necessary to unlock the key on `host1`. (IIRC I've been able to do that the other day with the key on `host1` being unencrypted.) – Alois Mahdal Feb 01 '19 at 23:46
  • I don't quite understand your setup, but would recommend using SSH keys for authentication, SSH agent and agent forwarding (`ssh -A`). – kynan Feb 02 '19 at 21:22
  • 1
    If you end up here as a Windows user, the ssh command just needs a little fine tuning to work: `ProxyCommand ssh.exe hostB -W %h:%p`, i.e., `ssh.exe` instead of `ssh`. As of this writing `ProxyJump` is not working yet on Windows (but might be fixed as [described here](https://fabian-voith.de/2020/04/16/using-openssh-on-windows-10-and-handling-passwords/)). – ToJo Dec 22 '21 at 22:54
78

OpenSSH v7.3 onward supports a -J switch and a ProxyJump option, which allow one or more comma-separated jump hosts, so, you can simply do this now:

ssh -J jumpuser1@jumphost1,jumpuser2@jumphost2,...,jumpuserN@jumphostN user@host
nikolay
  • 883
  • 6
  • 7
21

We have one ssh gateway into our private network. If I'm outside and want a remote shell on a machine inside the private network, I would have to ssh into the gateway and from there to the private machine.

To automate this procedure, I use the following script:

#!/bin/bash
ssh -f -L some_port:private_machine:22 user@gateway "sleep 10" && ssh -p some_port private_user@localhost

What is happening:

  1. Establish a tunnel for the ssh protocol (port 22) to the private machine.
  2. Only if this is successful, ssh into the private machine using the tunnel. (the && operater ensures this).
  3. After closing the private ssh session, I want the ssh tunnel to close, too. This is done via the "sleep 10" trick. Usually, the first ssh command would close after 10 seconds, but during this time, the second ssh command will have established a connection using the tunnel. As a result, the first ssh command keeps the tunnel open until the following two conditions are satisfied: sleep 10 is finished and the tunnel is no longer used.
20

After reading the above and glueing everything together, I've created the following Perl script (save it as mssh in /usr/bin and make it executable):

#!/usr/bin/perl

$iport = 13021;
$first = 1;

foreach (@ARGV) {
  if (/^-/) {
    $args .= " $_";
  }
  elsif (/^((.+)@)?([^:]+):?(\d+)?$/) {
    $user = $1;
    $host = $3;
    $port = $4 || 22;
    if ($first) {
      $cmd = "ssh ${user}${host} -p $port -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no";
      $args = '';
      $first = 0;
    }
    else {
      $cmd .= " -L $iport:$host:$port";
      push @cmds, "$cmd -f sleep 10 $args";
      $cmd = "ssh ${user}localhost -p $iport -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no";
      $args = '';
      $iport ++;
    }
  }
}
push @cmds, "$cmd $args";

foreach (@cmds) {
  print "$_\n";
  system($_);
}

Usage:

To access HOSTC via HOSTA and HOSTB (same user):

mssh HOSTA HOSTB HOSTC

To access HOSTC via HOSTA and HOSTB and use non-default SSH-portnumbers and different users:

mssh user1@HOSTA:1234 user2@HOSTB:1222 user3@HOSTC:78231

To access HOSTC via HOSTA and HOSTB and use X-forwarding:

mssh HOSTA HOSTB HOSTC -X

To access port 8080 on HOSTC via HOSTA and HOSTB:

mssh HOSTA HOSTB -L8080:HOSTC:8080
Bob Muller
  • 201
  • 2
  • 3
  • 1
    this is awesome – Mala Mar 25 '12 at 01:20
  • 1
    I seriously cannot thank you enough, this script makes my life easier on a daily basis. The only thing I changed was to add int(rand(1000)) to iport, to allow multiple instances to run at the same time. I definitely owe you a beer. – Mala May 03 '12 at 07:20
  • This works really well. A further improvement would be to resolve HOSTB, HOSTC etc using localhost's /etc/hosts and ~/.ssh/config – Steve Bennett Sep 23 '13 at 07:03
  • Also I second Mala's comment. Without the randomized port, if you then try to `mssh HOSTA HOSTD` you'll actually end up at HOSTB (and maybe won't realise..) – Steve Bennett Sep 24 '13 at 02:39
17

I did what I think you wanted to do with

ssh -D 9999 -J host1 host2

I'm prompted for both passwords, then I can use localhost:9999 for a SOCKS proxy to host2. It's the nearest I can think of to the example you showed in the first place.

Cheryl
  • 179
  • 1
  • 2
13

My answer is really the same as all the other answers here, but, I wanted to clarify the usefulness of ~/.ssh/config and ProxyJump.

To illustrate this, suppose I needed to get to a destination in 3 hops, and, for each hop, I needed to specify username, host, port, and identity. To get to the final computer I may have used multiple ssh commands and the identity files are stored on multiple computers:

[yourpc] $ ssh user1@host1 -p 22 -i ~/.ssh/pem/identity1.pem
[host1] $ ssh user2@host2 -p 22 -i ~/.ssh/pem/identity2.pem
[host2] $ ssh user3@host3 -p 22 -i ~/.ssh/pem/identity3.pem
[host3] $ exit
[host2] $ exit
[host1] $ exit
[yourpc] $

If I want to use a single ssh command to go from your PC to any of the hosts. I can use ~/.ssh/config config file which contains the details of each host and all identities needed to access each host on my host PC. The ProxyJump keyword is used to specify an intermediate host is needed to arrive at the target host.

Host hop1
    User user1
    HostName host1
    Port 22
    IdentityFile ~/.ssh/pem/identity1.pem

Host hop2
    User user2
    HostName host2
    Port 22
    IdentityFile ~/.ssh/pem/identity2.pem
    ProxyJump hop1

Host hop3
    User user3
    HostName host3
    Port 22
    IdentityFile ~/.ssh/pem/identity3.pem
    ProxyJump hop2

From your computer, you can test each jump individually, i.e.

[yourpc] $ ssh hop1 # will go from your PC to host1 in a single step
[host1] $ exit
[yourpc] $ ssh hop2 # will go from your PC to host2 via host1 (i.e. two steps)
[host2] $ exit
[yourpc] $ ssh hop3 # will go from your PC to host3 via host1 and host2 (i.e. three steps)
[host3] $ exit
[yourpc] $ 

Another cool thing about the ~/.ssh/config file is that this will also enable sftp file transfers via any series of hops, e.g.

[yourpc] $ sftp hop1 # for file transfers between your PC and host1
Connected to hop1.
sftp> quit
[yourpc] $ sftp hop2 # for file transfers between your PC and host2
Connected to hop2.
sftp> quit
[yourpc] $ sftp hop3 # for file transfers between your PC and host3
Connected to hop3.
sftp> quit

References:

Stephen Quan
  • 616
  • 7
  • 6
9

This answer is similar to kynan, as it involves the use of ProxyCommand. But it's more convenient to use IMO.

If you have netcat installed in your hop machines you can add this snippet to your ~/.ssh/config:

Host *+*
    ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:/ -p /') nc $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /')

Then

ssh -D9999 host1+host2 -l username

will do what you asked.

I came here looking for the original place where I read this trick. I'll post a link when I find it.

silviot
  • 341
  • 5
  • 13
  • 2
    I believe this is the origination of the trick: https://wiki.gentoo.org/wiki/SSH_jump_host – slm Mar 28 '14 at 21:00
4
ssh -L 9999:host2:80 -R 9999:localhost:9999 host1

-L 9999:host2:80

Means bind to localhost:9999 and any packet sent to localhost:9999 forward it to host2:80

-R 9999:localhost:9999

Means any packet received by host1:9999 forward it back to localhost:9999

chinmaya
  • 309
  • 1
  • 4
3

Only this helped me on more than two hosts:

ssh -L 6010:localhost:6010 user1@host1 \
-t ssh -L 6010:localhost:6010 user2@host2 \
-t ssh -L 6010:localhost:6010 user3@host3

It will prompt you with three passwords.

Inspired by this answer

vdi
  • 131
  • 3
1

In this answer I will go through a concrete example. You just need to replace computers' hostnames, usernames and passwords by yours.

Problem statement

Let's assume we have the following network topology:

our local computer <---> server 1 <---> server 2

For the sake of concreteness, let's assume we have the following computers' hostnames, usernames and passwords:

LocalPC            <--->  hostname: mit.edu         <---> hec.edu
                          username: bob                   username: john 
                          password: dylan123              password: doe456

Goal: we want to set up a SOCKS proxy that listens on port 9991 of LocalPC so that each time a connection on LocalPC is initiated from port 9991 it goes through mit.edu then hec.edu.

Example of use case: hec.edu has an HTTP server that is only accessible on http://127.0.0.1:8001, for security reasons. We would like to be able to visit http://127.0.0.1:8001 by opening a web browser on LocalPC.


Configuration

In LocalPC, add in ~/.ssh/config:

Host HEC
    HostName hec.edu
    User john
    ProxyCommand ssh bob@mit.edu -W %h:%p

Then in the terminal of LocalPC, run:

ssh -D9991 HEC

It will ask you the password of bob on mit.edu (i.e., dylan123), then it will ask you the password of john on hec.edu (i.e., doe456).

At that point, the SOCKS proxy is now running on port 9991 of LocalPC.

For example, if you want to visit a webpage on LocalPC using the SOCKS proxy, you can do in Firefox:

enter image description here

Some remarks:

  • in ~/.ssh/config, HEC is the connection name: you may change it to anything you want.
  • The -D9991 tells ssh to set up a SOCKS4 proxy on port 9991.
Franck Dernoncourt
  • 20,384
  • 48
  • 186
  • 322
1

The best solution is Jump Proxy:

ssh -N -A -J user1@host1 -D 8123 user2@host2

then you can use SOCKS in local via 127.0.0.1:8123

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 30 '22 at 08:50
1

you should be able to use port forwarding to access a service on host2 from localhost. A good guide is located here. Excerpt:

There are two kinds of port forwarding: local and remote forwarding. They are also called outgoing and incoming tunnels, respectively. Local port forwarding forwards traffic coming to a local port to a specified remote port.

For example, if you issue the command

ssh2 -L 1234:localhost:23 username@host

all traffic coming to port 1234 on the client will be forwarded to port 23 on the server (host). Note that localhost will be resolved by the sshdserver after the connection is established. In this case localhost therefore refers to the server (host) itself.

Remote port forwarding does the opposite: it forwards traffic coming to a remote port to a specified local port.

For example, if you issue the command

ssh2 -R 1234:localhost:23 username@host

all traffic which comes to port 1234 on the server (host) will be forwarded to port 23 on the client (localhost).

In your cast, replace localhost in the example with host2 and host with host1.

fideli
  • 14,636
  • 2
  • 35
  • 44
  • according to that article, the connection will only be secured until the middle machine (host1). Is there a way to make sure the whole thing stays secure? – Mala Jan 16 '10 at 07:21
  • I've never tried this, but if host1 and host2 are both ssh servers, you might be able to set up a tunnel from host1 to host2, then set up a tunnel from localhost to host1 for the same service (getting your local and remote ports right). I don't know if that's possible in one command from localhost. – fideli Jan 16 '10 at 16:13
0

The option 2 of the best answer could be used with different ssh users than the current one aka : user@host

    export local_host_port=30000
    export host1_user=xyz
    export host1=mac-host
    export host1_port=30000
    export host2=192.168.56.115
    export host2_user=ysg
    export host2_port=13306

    # Tunnel from localhost to host1 and from host1 to host2
    # you could chain those as well to host3 ... hostn
    ssh -tt -L $local_host_port:localhost:$host1_port $host1_user@$host1 \
    ssh -tt -L $host1_port:localhost:$host2_port $host2_user@$host2
Yordan Georgiev
  • 153
  • 1
  • 8
0

In my case I did

localhost$ ssh -D 9999 host1
host1$ ssh -L 8890:localhost:8890 host2

where host2:8890 is running on a Jupyter Notebook.

Then I configured Firefox to use localhost:9999 as a SOCKS host.

So now I've got the notebook running on host2 accessible by Firefox at localhost:8890 on my machine.

robinCTS
  • 4,327
  • 4
  • 20
  • 29
amarion
  • 101
0

The three options mentioned in the accepted answer didn't work for me at all. Since I don't have much permission over both hosts, and seem like our DevOps team has a pretty strict rules when comes to authentication and are doing MFA. Somehow the commands above cannot play well with our authentication.

The context is really similar to answers above though: I cannot ssh into production server directly, and have to do 1 hop using a jump server.

Yet Another Solution - a naive one

I ended up doing it by a very naive way: instead of trying to run all the commands on my laptop, I run the commands on each of the machine, as below:

  1. SSH into your jump server, then run ssh -v -L 6969:localhost:2222 -N your-protected.dest.server. If you're prompted with any password input, type it.
  2. Now on your laptop, run ssh -v -L 6969:localhost:6969 -N your-jump-server.host.name. This will forward any of your request on port 6969 on your laptop, to the jump server. Then by turn, since we configured in our previous step, the jump server will again forward requests of port 6969 to port 2222 on the protected destination server.

You should see the command "hangs" there after printing some message - it means they're working! One exception - you should not see error message like Could not request local forwarding., if you see that, then it's still not working :(. You can now try to fire request on port 6969 from your laptop, and see if it's working.

Hopefully if you are someone who failed all the methods above, perhaps you can try this.

Shawn
  • 101
  • 1
0

Add the following to your ~/.ssh/config.

Host jump_host
  HostName example.com
  User <jump_host_user>
  IdentityFile <local/path/to/jump_id>

Host destination_host
  HostName <ip or url>
  User <destination_host_user>
  IdentityFile <local/path/to/id>
  ProxyJump jump_host

This will make the destination act as if you had direct access so that you can call

ssh -D9999 destination_host

Using these of course requires, that you add your id_rsa.pub into the authorized_keys file on the respective serves.

0-_-0
  • 259
  • 2
  • 5
0

If you can SSH into both machines, take a look at ssh's ProxyCommand directive. This will let you go straight from localhost into host2 (in one easy command if you use public keys!!). Then you can do whatever you want with host2.

http://www.statusq.org/archives/2008/07/03/1916/