Most SSH keepalive advice stops at "add ServerAliveInterval 30" and leaves the useful parts out. The keyword is a client-side timer that only fires when the channel has been quiet, it counts unanswered probes rather than seconds, and it lives in a file where the first matching value wins. Get those three properties wrong and a healthy VPN session still drops, while a dead one lingers.

The two directions belong to different daemons

ServerAliveInterval is read by your ssh client. ClientAliveInterval is read by sshd on the far side and probes the other way, from server to client. They are not aliases and they do not cooperate, so setting one has no effect on the other end's behaviour. Mixing them up is the usual reason a fix appears to work on one host and not on the next.

Both accept zero. At zero the client sends nothing at all, and on the server side a ClientAliveCountMax of zero disables the disconnect that would otherwise follow unanswered checks.

What a probe proves, and what it does not

After the configured interval passes with no data arriving from the server, the client sends a small request through the existing encrypted channel and waits for a reply. ServerAliveCountMax sets how many of those may go unanswered before the session is torn down. With an interval of 60 and a count of 3 the client gives up somewhere around three minutes of silence, and that figure is a property of your configuration rather than a network standard. Leave the interval at its default of 0 and no probe is ever sent, which is why a stock client can sit on a dead connection indefinitely.

Because the exchange runs inside the SSH transport, a reply proves that the peer's SSH process is still reading and writing. It says nothing about the program you started inside that session: a database client, a forwarded port or a long-running script can be wedged while the transport answers probes happily.

TCPKeepAlive is a separate mechanism one layer down. The kernel handles it, it is not encrypted, and a peer whose SSH daemon is stuck can still satisfy it. That is why the SSH-level probe is the more meaningful signal when you care about the session rather than the socket.

Ordering decides which value survives

OpenSSH applies the first value it obtains for each parameter, so a specific Host block must appear before any broader block that would also match. Put wildcard defaults at the bottom of the file, not the top, and remember that a Match block further down cannot override a value that has already been set. The OpenSSH client configuration reference states the first-value rule directly, which is worth reading once before reorganising a shared config.

Command-line options win over the file, which makes a temporary experiment cheap:

ssh -o ServerAliveInterval=15 -o ServerAliveCountMax=4 office-shell

To see what the client actually resolved instead of what you think you wrote, ask it to dump the effective configuration. The output holds the merged values for that hostname and is the fastest way to catch a stray earlier block:

ssh -G office-shell | grep -i alive

What keepalives do over a tunnel

Periodic traffic keeps a stateful NAT or firewall mapping from expiring, which is a genuine benefit on a VPN whose gateway drops idle flows after a few minutes. Probes arriving every 60 seconds refresh that mapping without carrying any application data.

They cannot rebuild a path that has gone away. When the tunnel interface disappears the probes fail like everything else, and the session ends once the count is exhausted. Liveness detection and path recovery are different problems, and only the first one is configurable in ssh_config. What the interval and count actually buy you is how quickly the client notices, which is the part a user experiences as a hang.

Multiplexed connections add a wrinkle. With ControlMaster in play the probes belong to the master connection, and a new session over an existing master inherits whatever that master negotiated. If the mux master is wedged, opening another window will not test the path again.

Confirming the setting end to end

On the server, dump the effective daemon configuration rather than reading the file and guessing which include won:

sshd -T | grep -i clientalive

Then watch a real session with debug output enabled. The client prints a timeout line when the count runs out, and the timestamps around it show whether the silence was continuous or the probes were never sent at all. A session that dies with no probe traffic usually has the keyword in a block that never matched.

Two checks are worth adding for any host you reach only through a tunnel: confirm the resolved interval is the one you intended, and confirm that interval multiplied by the count is shorter than the idle timeout on the VPN gateway. When the second number is larger, the gateway silently drops the flow first and the SSH probe never gets a chance to report anything.

អត្ថបទពាក់ព័ន្ធ