I run these two commands all the time to connect to my rds instance on aws that's protected behind a firewall (so i tunnel through the ec2 instance) like so:

command 1: open the tunnel (run on background)

ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &

command 2: connect to db through tunnel port:

PGPASSWORD=password psql dbname -U user -h ip_address -p port;

which is awesome, but I would like to put both these in a single function. But nothing worked with me:

attempt 1: run without background stuff:

function db()
    ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &
    PGPASSWORD=password psql dbname -U user -h ip_address -p port;

simply tells me this:

[1] 62924
psql: could not connect to server: Connection refused
    Is the server running on host "" and accepting
    TCP/IP connections on port 6666?

although the initial command is running in the background:

ps aux | grep host
(standard input):435:abdullah         62924   0.0  0.0  4315660   5828 s006  S     3:06PM   0:00.03 ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

and if i immediately run the next command after it.. i connect to the db just fine!

PGPASSWORD=password psql dbname -U user -h ip_address -p port;

how do I make this work?


The first command didn't have time to establish a tunnel when the second command was run, thus giving a "Connection refused".

Just don't use & but use instead the option -f:

Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm.
If the ExitOnForwardFailure configuration option is set to “yes”, then a client started with -f will wait for all remote port forwards to be successfully established before placing itself in the background.

Putting all this together, replace the ssh line in the function with:

ssh -o ExitOnForwardFailure=yes -f -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

that way running the function multiple times, won't leave (multiple-1) useless ssh running.

It's also possible to replace -N with a short remote sleep command. That way there won't be a long lived idle ssh command that has to be searched if it has to be killed. Ssh will still wait the end of tunnel usage before ending, so the short delay is not an issue:

ssh -o ExitOnForwardFailure=yes -f -L port:host:5432 user@$ip -i ~/.ssh/key.pub sleep 15
  • Great answer.. but can you please explain the || : part or is it a typo? – abbood Apr 14 '18 at 11:35
  • If you set the shell -e option somewhere, a failure would cancel everything. : is no-op (a shorter true command). so using a || : construct just locally disables -e. ssh is expected to fail on second invocation, because the former ssh is still running (it forked like a daemon and won't die). anyway it's possibly useless to put it, perhaps -e is already disabled in a function. it's also possible to replace -N with a short sleep command: ssh would still wait the end of the tunnel usage before exiting – A.B Apr 14 '18 at 11:40
  • What happens if the first invocation fails? Ie my tunnel credentials are invalid? Would that immediately abort everything? – abbood Apr 14 '18 at 11:43
  • pgsql would complain like before "Connection refused" – A.B Apr 14 '18 at 11:44
  • @abbood added an alternate option – A.B Apr 14 '18 at 12:04

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.