260

I'm currently running a bunch of:

sudo ssh -L PORT:IP:PORT root@IP

where IP is the target of a secured machine, and PORT represents the ports I'm forwarding.

This is because I use a lot of applications which I cannot access without this forwarding. After performing this, I can access through localhost:PORT.

The main problem occured now that I actually have 4 of these ports that I have to forward.

My solution is to open 4 shells and constantly search my history backwards to look for exactly which ports need to be forwarded etc, and then run this command - one in each shell (having to fill in passwords etc).

If only I could do something like:

sudo ssh -L PORT1+PORT2+PORT+3:IP:PORT+PORT2+PORT3 root@IP

then that would already really help.

Is there a way to make it easier to do this?

14 Answers 14

410

The -L option can be specified multiple times within the same command. Every time with different ports. I.e. ssh -L localPort0:ip:remotePort0 -L localPort1:ip:remotePort1 ...

4
  • 36
    At first I didn't understand this answer. So posting an example here in case anyone suffers the same. The author meant "ssh -L port0:ip:port0 -L port1:ip:port1 ..."
    – Mong H. Ng
    Commented Aug 14, 2019 at 20:29
  • 9
    And of course the port on the left side is local, on the right side remote. Ex. ssh -L localPort0:IP:remotePort0 -L localPort1:IP:remotePort1
    – defines
    Commented Nov 9, 2020 at 18:13
  • Can this be done with a reverse tunnel also?
    – Dark Star1
    Commented Jun 28, 2021 at 9:02
  • 1
    @DarkStar1 yes! I just tested it and it works :) Commented Aug 12, 2021 at 1:01
126

Exactly what NaN answered, you specify multiple -L arguments. I do this all the time. Here is an example of multi port forwarding:

ssh remote-host -L 8822:REMOTE_IP_1:22 -L 9922:REMOTE_IP_2:22

Note: This is same as -L localhost:8822:REMOTE_IP_1:22 if you don't specify localhost.

Now with this, you can now (from another terminal) do:

ssh localhost -p 8822

to connect to REMOTE_IP_1 on port 22

and similarly

ssh localhost -p 9922

to connect to REMOTE_IP_2 on port 22

Of course, there is nothing stopping you from wrapping this into a script or automate it if you have many different host/ports to forward and to certain specific ones.

2
  • 2
    Be careful about this: "Note: This is same as -L localhost:8822:REMOTE_IP_1:22 if you don't specify localhost." This is only true if the GatewayPorts setting is 'no', which is admittedly the default. But considering the implications if it's not, you should verify the setting or, better still, be explicit and use "-L localhost:8822...".
    – David
    Commented Jul 31, 2019 at 20:22
  • 2
    I agree with @David By default, anyone (even on different machines) can connect to the specified port on the SSH client machine. However, this can be restricted to programs on the same host by supplying a bind address: ssh -L 127.0.0.1:80:intra.example.com:80 gw.example.com ssh.com/ssh/tunneling/example
    – Karl Pokus
    Commented Oct 15, 2019 at 19:36
70

For people who are forwarding multiple ports through the same host can set up something like this in their ~/.ssh/config

Host all-port-forwards
  Hostname 10.122.0.3
  User username
  LocalForward PORT_1 IP:PORT_1
  LocalForward PORT_2 IP:PORT_2
  LocalForward PORT_3 IP:PORT_3
  LocalForward PORT_4 IP:PORT_4

and it becomes a simple ssh all-port-forwards away.

3
  • I like this approach.
    – BMW
    Commented Jan 2, 2020 at 7:58
  • This is by far the best method, and I'd guess most users will have just one remote server with nginx proxies to access their CGNAT-shielded home ports etc.
    – Julius
    Commented May 5, 2021 at 10:24
  • 2
    ( of course, just to be complete, the example I just mentioned is with the -R instead of -L option, i.e. RemoteForward instead of LocalForward )
    – Julius
    Commented May 5, 2021 at 11:06
32

You can use the following bash function (just add it to your ~/.bashrc):

function pfwd {
  for i in ${@:2}
  do
    echo Forwarding port $i
    ssh -N -L $i:localhost:$i $1 &
  done  
}

Usage example:

pfwd hostname {6000..6009}
3
  • 4
    Use -f to run in background
    – Karl Pokus
    Commented Oct 15, 2019 at 19:38
  • 2
    Ehmmm... why do you want do it in that way? Commented Jan 2, 2020 at 21:44
  • That opens a separate tunnel for each port. Not efficient. Commented Apr 13, 2023 at 17:19
9

jbchichoko and yuval have given viable solutions. But jbchichoko's answer isn't a flexible answer as a function, and the opened tunnels by yuval's answer cannot be shut down by ctrl+c because it runs in the background. I give my solution below solving both the two flaws:

Defing a function in ~/.bashrc or ~/.zshrc:

# fsshmap multiple ports
function fsshmap() {
  echo -n "-L 1$1:127.0.0.1:$1 " > $HOME/sh/sshports.txt
  for ((i=($1+1);i<$2;i++))
  do
    echo -n "-L 1$i:127.0.0.1:$i " >> $HOME/sh/sshports.txt
  done
  line=$(head -n 1 $HOME/sh/sshports.txt)
  cline="ssh "$3" "$line
  echo $cline
  eval $cline
}

A example of running the function:

fsshmap 6000 6010 hostname

Result of this example:

You can access 127.0.0.1:16000~16009 the same as hostname:6000~6009

7

In my company both me and my team members need access to 3 ports of a non-reachable "target" server so I created a permanent tunnel (that is a tunnel that can run in background indefinitely, see params -f and -N) from a reachable server to the target one. On the command line of the reachable server I executed:

ssh root@reachableIP -f -N  -L *:8822:targetIP:22  -L *:9006:targetIP:9006  -L *:9100:targetIP:9100

I used user root but your own user will work. You will have to enter the password of the chosen user (even if you are already connected to the reachable server with that user).

Now port 8822 of the reachable machine corresponds to port 22 of the target one (for ssh/PuTTY/WinSCP) and ports 9006 and 9100 on the reachable machine correspond to the same ports of the target one (they host two web services in my case).

0
5

Another one liner that I use and works on debian:

ssh [email protected] $(for j in $(seq 20000 1 20100 ) ; do  echo " -L$j:127.0.0.1:$j " ; done | tr -d "\n")
1
  • BTW I don't think you need the tr -d "\n" at the end. For me, the subshell simply parses into new args. Something like this works for me (for Jupyter notebooks): ssh somewhere $(for i in $(seq 8888 8898); do echo "-L ${i}:localhost:${i}"; done) Commented Jan 11, 2022 at 0:24
3

One of the benefits of logging into a server with port forwarding is facilitating the use of Jupyter Notebook. This link provides an excellent description of how to it. Here I would like to do some summary and expansion for all of you guys to refer.

Situation 1. Login from a local machine named Host-A (e.g. your own laptop) to a remote work machine named Host-B.

ssh user@Host-B -L port_A:localhost:port_B
jupyter notebook --NotebookApp.token='' --no-browser --port=port_B

Then you can open a browser and enter: http://localhost:port_A/ to do your work on Host-B but see it in Host-A.

Situation 2. Login from a local machine named Host-A (e.g. your own laptop) to a remote login machine named Host-B and from there login to the remote work machine named Host-C. This is usually the case for most analytical servers within universities and can be achieved by using two ssh -L connected with -t.

ssh -L port_A:localhost:port_B user@Host-B -t ssh -L port_B:localhost:port_C user@Host-C
jupyter notebook --NotebookApp.token='' --no-browser --port=port_C

Then you can open a browser and enter: http://localhost:port_A/ to do your work on Host-C but see it in Host-A.

Situation 3. Login from a local machine named Host-A (e.g. your own laptop) to a remote login machine named Host-B and from there login to the remote work machine named Host-C and finally login to the remote work machine Host-D. This is not usually the case but might happen sometime. It's an extension of Situation 2 and the same logic can be applied on more machines.

ssh -L port_A:localhost:port_B user@Host-B -t ssh -L port_B:localhost:port_C user@Host-C -t ssh -L port_C:localhost:port_D user@Host-D
jupyter notebook --NotebookApp.token='' --no-browser --port=port_D

Then you can open a browser and enter: http://localhost:port_A/ to do your work on Host-D but see it in Host-A.

Note that port_A, port_B, port_C, port_D can be random numbers except common port numbers listed here. In Situation 1, port_A and port_B can be the same to simplify the procedure.

1
  • A reminder, same port on different servers are different ports. Hence it could always make things easier by specifying an identical port number!
    – Fei Yao
    Commented Apr 4, 2020 at 10:17
3

Here is a solution inspired from the one from Yuval Atzmon.

It has a few benefits over the initial solution:

  • first it creates a single background process and not one per port
  • it generates the alias that allows you to kill your tunnels
  • it binds only to 127.0.0.1 which is a little more secure

You may use it as:

  • tnl your.remote.com 1234
  • tnl your.remote.com {1234,1235}
  • tnl your.remote.com {1234..1236}

And finally kill them all with tnlkill.

function tnl {
  TUNNEL="ssh -N "
  echo Port forwarding for ports:
  for i in ${@:2}
  do
    echo " - $i"
    TUNNEL="$TUNNEL -L 127.0.0.1:$i:localhost:$i"
  done
  TUNNEL="$TUNNEL $1"
  $TUNNEL &
  PID=$!
  alias tnlkill="kill $PID && unalias tnlkill"
}
3

An alternative approach is to tell ssh to work as a SOCKS proxy using the -D flag.

That way you would be able to connect to any remote network address/port accesible through the ssh server as long as the client applications are able to go through a SOCKS proxy (or work with something like socksify).

2

If you want a simple solution that runs in the background and is easy to kill - use a control socket

# start
$ ssh -f -N -M -S $SOCKET -L localhost:9200:localhost:9200 $HOST
# stop
$ ssh -S $SOCKET -O exit $HOST
1

I've developed loco for help with ssh forwarding. It can be used to share ports 5000 and 7000 on remote locally at the same ports:

pip install loco

loco listen SSHINFO -r 5000 -r 7000
0

First It can be done using Parallel Execution by xargs -P 0.

Create a file for binding the ports e.g.

localhost:8080:localhost:8080
localhost:9090:localhost:8080

then run

xargs -P 0  -I xxx ssh -vNTCL xxx <REMOTE>  < port-forward

or you can do a one-liner

echo localhost:{8080,9090} | tr ' ' '\n' | sed 's/.*/&:&/' | xargs -P 0 -I xxx ssh -vNTCL xxx <REMOTE>

pros independent ssh port-forwarding, they are independent == avoiding Single Point of Failure
cons each ssh port-forwarding is forked separately, somehow not efficient


second it can be done using curly brackets expansion feature in bash

echo "ssh -vNTC $(echo  localhost:{10,20,30,40,50} | perl -lpe 's/[^ ]+/-L $&:$&/g') <REMOTE>"
# output
ssh -vNTC -L localhost:10:localhost:10 -L localhost:20:localhost:20 -L localhost:30:localhost:30 -L localhost:40:localhost:40 -L localhost:50:localhost:50 <REMOTE>

real example

echo "-vNTC $(echo localhost:{8080,9090} | perl -lpe 's/[^ ]+/-L $&:$&/g') gitlab" | xargs ssh

Forwarding 8080 and 9090 to gitlab server.

pros one single fork == efficient
cons by closing this process (ssh) all forwarding are closed == Single Point of Failure

-1

You can use this zsh function (probably works with bash, too)(Put it in ~/.zshrc):

ashL () {
    local a=() i
    for i in "$@[2,-1]"
    do
        a+=(-L "${i}:localhost:${i}")
    done
    autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -NT "$1" "$a[@]"
}

Examples:

ashL [email protected] 6480 7690 7477

ashL [email protected] {6000..6050} # Forwards the whole range. This is simply shell syntax sugar.

Not the answer you're looking for? Browse other questions tagged or ask your own question.