Your tunnel is not unstable. It is being killed.

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

The process is alive. The tunnel is not.

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

What kills a tunnel, and what keeps it alive.

The causeThe symptomWhat fixes it
Silent connection cut in transitdies after 5–30 min with no trafficServerAliveInterval 30 and ServerAliveCountMax 3
The server enforces its timeoutdies at a fixed time, whatever you doserver side: ClientAliveInterval, otherwise only the client keepalive helps
The machine went to sleepdead on wake, every timenothing 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 runExitOnForwardFailure yes, to fail outright rather than halfway
The tunnel looks open but refusesconnection refused right after launchingwait 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

Four lines in ~/.ssh/config.

They apply to all your hosts, and they are enough in most cases: including if you never install Kestro.

  1. ServerAliveInterval 30

    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.

  2. ServerAliveCountMax 3

    After three unanswered signals, ssh gives up instead of clinging to a dead connection. You then know it dropped, which beats a ghost tunnel.

  3. ExitOnForwardFailure yes

    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.

  4. TCPKeepAlive yes

    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

Everything stays with you.

Your connections run from your computer to your servers, never through us.

  • No account

    Nothing to create, no password.

  • Your keys stay put

    SSH, Google Cloud, AWS: Kestro uses your own tools, locally.

  • No usage statistics

    We don’t know what you open, or when.

  • One exception

    Your licence renews with us. Nothing else leaves.

What config cannot fix

Sleep, and the lie of the green dot.

Two problems no ssh option addresses, because they are outside its remit.

  • Closing the laptop kills the tunnel

    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.

  • A live process is not an open port

    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.

  • And if you just want it to work

    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

You pay once. That’s it.

A tool you open forty times a day shouldn’t send an invoice every month.

Kestro licence

€29.99$29.99£25.99CHF 25.90CA$40.99A$45.99€39.99$39.99£34.99CHF 33.90CA$54.99A$61.99 · Launch offer · upcoming price

1 computer · one-time

  • Every feature of the app, no tiers, no “Pro” edition
  • One year of updates included, then renew if you want to
  • The app stays yours, renewed or not
  • One key, the same on all your computers
  • Switch machines whenever you like, no limit
  • No account to create: the key arrives by email

Or download it for free first →

Refunded within 14 days, no questions asked.

Questions we get asked

Where do these options go?

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.

ServerAliveInterval or ClientAliveInterval?

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.

Doesn’t autossh already do all this?

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.

My Cloud SQL tunnel drops too: same thing?

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.