I've been browsing through answers here on superuser, however no answers I found addresses my use case.

I want to chain two commands I use that let me tunnel through a tunnel:

ssh -L 22:hostB:22 user@hostA


ssh -L 10002:localhost:10001 user@localhost

Right now what I do is enter the first command, open another terminal, and enter the second one. If I do something like

ssh … && ssh …

the second command executes only after I "exit" from the first connection.

More advanced ssh options like jumphost do not work due to server configuration on hostA, so I'm pretty sure this is the only option to have 10001 port accessible like this.

Is there a way to chain those two commands into one alias, or use some simple bash script with timeouts, etc?

  • Could you provide more detail about why JumpHost does not work? It uses exactly the same SSHv2 "direct-tcpip" request as -L does... – grawity Jun 29 at 12:11
  • Well I get permission denied on hostA if I try something with -J flag. My admin (it's a company network) told me to use it in such a way instead of JumpHost. – Ne Nenne Jun 29 at 12:28
  • Does that happen during authentication, or afterwards? Could you provide ssh -v logs of that happening? – grawity Jun 29 at 14:03
  • After authenticating to the jump host. pastebin.ubuntu.com/p/6SHsF2JfS4 – Ne Nenne Jun 29 at 14:34
  • That's odd, because -J or JumpHost basically expands to ssh hostA -W hostB:22 which is nearly indistinguishable from ssh hostA -N -L 12345:hostB:22... – grawity Jun 29 at 15:52

I assume you cannot do just ssh -L 10002:hostB:10001 user@hostA because

  • either whatever listens on B's 10001 binds to the local interface only,
  • or the firewall on B disallows connection to hostB:10001 from A,
  • or whatever.

ssh … && ssh …

the second command executes only after I "exit" from the first connection.

Yes, this is normal. The part before && must terminate and return exit status before the next command is run (or not, depending on the status).

This will run the second ssh almost immediately but it's flawed:

( ssh … & ) && ssh …

It's flawed because the first ssh has no impact on the second one. The second one will probably run way before the first one establishes the tunnel. Asking for passwords may also get problematic.

For this usage case ssh has -f option. From man 1 ssh:

Requests ssh to go to background just before command execution. […]

So the basic command may be:

ssh -fNL 22:hostB:22 user@hostA && ssh -NL 10002:localhost:10001 user@localhost

It's up to you whether you want to use -N with the second ssh or not. Now the trick is the first ssh forks to the background after asking for password (if applicable) and after establishing the first tunnel, so temporarily there are two "first" ssh processes. The one in the background starts handling the tunnel; the one in the foreground exits, this allows && to work.

You probably want -o ExitOnForwardFailure=yes. If the first tunnel cannot be established, the first ssh will exit unsuccessfully and the second one won't run. The improved command is:

ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
    user@hostA &&
ssh -NL 10002:localhost:10001 user@localhost

Note when the second ssh exits, the first one (in the background) remains. The advantage is you can run the sole second ssh later. The disadvantage is you'll need to kill the first one manually if you don't want it to remain. With this in mind consider the following approach:

ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
ssh -NL 10002:localhost:10001 user@localhost

Note there is no &&. Now if there is the old first ssh still running then the new one will fail because it won't be able to bind to the port. The second ssh will run regardless, it will use the first tunnel and it doesn't matter how old the tunnel is.

It may happen there is no old ssh and the first ssh fails for whatever reasons. If so, the second one will run and it will probably fail (connection refused) because there's nothing listening on the port 22.

Still you may not want to leave an "idle" ssh process, you may want to terminate it automatically after the second ssh exits. Obviously killall ssh may cause collateral damage. Let's find something more subtle:

Places the ssh client into “master” mode for connection sharing. […]


-O ctl_cmd
Control an active connection multiplexing master process. […]


-S ctl_path
Specifies the location of a control socket for connection sharing […]



ssh -fNL 22:hostB:22 \
    -o ExitOnForwardFailure=yes \
    -MS "$socket"
    user@hostA &&
ssh -NL 10002:localhost:10001 user@localhost
ssh -S "$socket" -O exit dummy

After the second ssh terminates, the third one will tell the first one to exit. In the third ssh command it's the socket that matters, not the hostname; still you need to provide some hostname, hence dummy. The first ssh will remove the socket before exiting, there should be no garbage left.

There are still few concerns:

  • If the script is interrupted, the first ssh will remain. Either drop && or send -O exit at the very beginning just in case (it would be the zeroth ssh).
  • If the first ssh dies abruptly, the socket may remain. It will make any new first ssh fail. A good idea may be to expand the zeroth ssh like this:

    ssh -S "$socket" -O exit dummy || rm "$socket"
  • /tmp/hostA.socket may not be the best location for the socket. In ssh_config the equivalent of -S command line option is ControlPath. See what man 5 ssh_config says about it:

    It is recommended that any ControlPath used for opportunistic connection sharing include at least %h, %p, and %r (or alternatively %C) and be placed in a directory that is not writable by other users.

    A "directory that is not writable by other users" may be /run/user/$UID, if your OS supports it (I believe it's systemd's thing).

  • What an amazing and informative answer, thank you very much. It works flawlessly. – Ne Nenne Jun 30 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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