What you see
$ ssh -N -L 5433:10.0.3.4:5432 bastion # no output, all good # twenty minutes later $ psql -h localhost -p 5433 connection refused $ ps aux | grep ssh # the process is still there
An ssh -L that dies “on its own” almost always dies for one of three known reasons: nobody is talking on the connection, the server enforces its own timeout, or the machine went to sleep. All three are fixable, and this page gives the exact options before talking about anything else.
macOS 11+ · Windows soon
What is actually happening
That gap is what makes it feel unstable: nothing crashed, and yet nothing gets through.
What you see
$ ssh -N -L 5433:10.0.3.4:5432 bastion # no output, all good # twenty minutes later $ psql -h localhost -p 5433 connection refused $ ps aux | grep ssh # the process is still there
What happened
A firewall cut a silent connection · NAT, load balancer, corporate VPN or the server applied ClientAliveMaxCount or the Mac went to sleep · the socket dies, the process does not Nobody told you.
The three causes
| The cause | The symptom | What fixes it |
|---|---|---|
| Silent connection cut in transit | dies after 5–30 min with no traffic | ServerAliveInterval 30 and ServerAliveCountMax 3 |
| The server enforces its timeout | dies at a fixed time, whatever you do | server side: ClientAliveInterval, otherwise only the client keepalive helps |
| The machine went to sleep | dead on wake, every time | nothing on the ssh side: it must be relaunched, or something must relaunch it |
| The local port stays taken | “Address already in use” on the next run | ExitOnForwardFailure yes, to fail outright rather than halfway |
| The tunnel looks open but refuses | connection refused right after launching | wait for the port to listen: the process starts before the door opens |
Options documented in ssh_config(5) and sshd_config(5). Recorded on 7 August 2026; they have not changed in years and will not change soon.
The config that fixes the first two
They apply to all your hosts, and they are enough in most cases: including if you never install Kestro.
Sends a signal every thirty seconds. The connection stops being silent, and firewalls that drop idle sessions no longer have a reason to drop it.
After three unanswered signals, ssh gives up instead of clinging to a dead connection. You then know it dropped, which beats a ghost tunnel.
Without this line, ssh opens the session even when port forwarding fails: you get a live process and no tunnel. With it, the failure is plain.
Belt as well as braces: the operating system’s keepalive on top of ssh’s own, for network gear that ignores the latter.
On your machine
Your connections run from your computer to your servers, never through us.
Nothing to create, no password.
SSH, Google Cloud, AWS: Kestro uses your own tools, locally.
We don’t know what you open, or when.
Your licence renews with us. Nothing else leaves.
What config cannot fix
Two problems no ssh option addresses, because they are outside its remit.
No setting survives sleep: the socket is closed by the system. The command has to be run again, and above all, you have to know it does, which nothing tells you on wake. That is exactly what Kestro does: it reopens tunnels when the machine wakes, backing off progressively, and writes it on the row.
An ssh -L takes one to three seconds to authenticate and then listen. During that window, any script testing “is the process running?” answers yes, and the connection fails anyway. Kestro probes the local port and only shows “open” on the first successful connect: green never arrives early.
The four lines above are free and yours: copy them, they will settle most cases. Kestro is for people with several tunnels, a machine that sleeps, and better things to do than check which ones are still alive.
Pricing
A tool you open forty times a day shouldn’t send an invoice every month.
Kestro licence
1 computer · one-time
Or download it for free first →
Refunded within 14 days, no questions asked.
Questions we get asked
In ~/.ssh/config, under a Host matching the relevant machine, or under Host * so they apply everywhere. They take effect on the next connection, with nothing to restart. If you go through a bastion, put them on the bastion: that is the connection carrying the tunnel.
The first is client side, in your ~/.ssh/config, and it is the one you control. The second is server side, in sshd_config, and it lets the server shed silent clients. If the server is doing the cutting and you have no access to it, only ServerAliveInterval can compensate: by making sure you are never silent.
It restarts what drops, yes, and it is a good tool. It still will not tell you a local port is genuinely listening, it does not wake with the Mac, and it knows nothing about tunnels that are not SSH: a Cloud SQL proxy, an AWS session. Depending on what you open, it may be perfectly sufficient.
No, the causes differ: the Cloud SQL proxy has its own session handling and identity tokens, which expire for other reasons. The page on the proxy refusing connections covers that case.