Why my SSH jump host was silently dropping connections after an OpenSSH 9.5 update and how I fixed it

SSH jump host silently dropping connections after OpenSSH 9.5

A recent OpenSSH 9.5 release tweaked how the server handles TCP keepalive packets. My jump host – a modest Ubuntu 24.04 box – started dropping established SSH sessions after a few minutes of idle time. The problem popped up during a routine audit when several users complained of “connection closed” messages, yet nothing appeared in the logs.

What changed in 9.5?

OpenSSH 9.5 introduced new defaults for ClientAliveInterval and ClientAliveCountMax on the server side:

ClientAliveInterval 0
ClientAliveCountMax 3

With ClientAliveInterval set to zero, the server stops sending keepalive packets altogether. If a client stays idle, the server will close the session after ClientAliveCountMax retransmissions of the last packet it received. In practice, that means a silent drop after roughly 3–5 minutes on a congested network.

How I detected the problem

  1. Client logs – ssh -vvv user@jump printed Connection closed by remote host without any server‑side message.
  2. Server logs – /var/log/auth.log had no disconnect entry.
  3. Network capture – tcpdump -i eth0 port 22 showed the client sending a keepalive, but the server never replied.

The lack of a server log entry made it clear that the session was terminated by the server’s internal timeout logic, not by an explicit exit.

Quick fix: restore the old keepalive behaviour

The easiest way to bring back the familiar behaviour is to re‑enable the keepalive mechanism:

sudo tee /etc/ssh/sshd_config.d/keepalive.conf <<'EOF'
ClientAliveInterval 60
ClientAliveCountMax 3
EOF
sudo systemctl reload sshd

Now the server sends a keepalive every 60 seconds, matching OpenSSH 9.4 and earlier. The session will survive idle periods of up to three minutes, which is plenty for most jump‑host use cases.

A more robust approach: use ServerAliveInterval on the client

If you don’t want to touch the server, just set a client‑side keepalive:

ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 user@jump

Or put it in ~/.ssh/config:

Host jump
    ServerAliveInterval 60
    ServerAliveCountMax 3

This keeps the connection alive without modifying the server defaults – handy when you’re using a cloud‑managed bastion.

Why the change matters for security

Tightening the defaults reduces the window during which an idle session can be hijacked. The downside is that legitimate sessions may drop, especially on high‑latency or lossy links. To balance usability and security:

  • Monitoring – run sshd -T | grep clientalive after a reload to see the effective values.
  • Logging – set LogLevel VERBOSE if you need audit trails for keepalive failures.
  • Testing – use ssh -vvv to confirm behaviour after any change.

Trade‑offs to consider

OptionProsCons
Server‑side keepalive (default 9.5)Lower resource usage, tighter securitySilent drops, hard to debug
Re‑enable old keepaliveFamiliar behaviour, easy to troubleshootSlightly higher resource usage
Client‑side keepaliveNo server changes, flexible per‑userRequires client configuration, may not work for all users

For internal jump hosts, re‑enabling the old keepalive is usually the fastest route to stability. For shared or managed bastions, client‑side keepalive is safer.

Checklist for future upgrades

  1. Read the release notes – OpenSSH’s changelog often flags behavioural changes that affect long‑running connections.
  2. Test in staging – Spin up a copy of the jump host and run ssh -vvv for a few hours.
  3. Document the config – Store keepalive settings in version‑controlled files (/etc/ssh/sshd_config.d/).
  4. Monitor session drops – journalctl -u sshd or auditd can catch unexpected disconnects early.

Follow these steps and you’ll keep your jump host reliable without compromising on security.

See also