| Tested on | RHEL 10.2 (Coughlan) — vm1.lab.example (192.168.56.116) as SSH server; vm2.lab.example (192.168.56.220, connects from 192.168.56.156) as SSH client |
|---|---|
| Package | openssh-server 9.9p1-25.el10_2 and openssh-clients 9.9p1-25.el10_2 on both lab hosts |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Fedora, Ubuntu, Debian, and other Linux systems with OpenSSH |
| Privilege | sudo or root on the SSH server for sshd_config; normal user on the SSH client for ~/.ssh/config |
| Scope | Server-side ClientAliveInterval, ClientAliveCountMax, and TCPKeepAlive; client-side ServerAliveInterval and ServerAliveCountMax; reading effective values with sshd -T and ssh -G. Does not replace terminating a hung interactive SSH session or firewall idle rules on middleboxes. |
| Related guides | ssh command List active SSH connections OpenSSH authentication methods SSH multiplexing Test SSH connection |
“Idle SSH” can mean two different things: the user has stopped interacting with the shell, or the connection has stopped carrying network traffic long enough for a NAT or firewall to expire it. OpenSSH uses different settings for these cases. Server admins and remote users often want opposite outcomes: drop ghost sessions on the server, or keep a quiet laptop session alive through a home router.
OpenSSH splits the work cleanly. sshd settings on the server decide when to probe or drop a client. ssh client settings on the workstation that runs ssh decide whether the client sends its own keepalive traffic. This guide tests both sides on vm1.lab.example (server) and vm2.lab.example (client).
Server settings vs client settings
Misplacing these directives is the most common mistake on this topic. Edit the SSH server when you control sshd; edit the SSH client when your session dies on the path between your laptop and the server.
| Goal | Where | Directives | File |
|---|---|---|---|
| Detect and drop unresponsive clients | SSH server | ClientAliveInterval, ClientAliveCountMax, optionally TCPKeepAlive |
/etc/ssh/sshd_config or /etc/ssh/sshd_config.d/*.conf |
| Keep a quiet session alive through NAT or firewalls | SSH client | ServerAliveInterval, ServerAliveCountMax |
/etc/ssh/ssh_config, /etc/ssh/ssh_config.d/*.conf, or ~/.ssh/config |
| Close a quiet shell channel when its traffic stops (OpenSSH 9+) | SSH server | ChannelTimeout session=… |
/etc/ssh/sshd_config drop-in |
All procedural steps below name which lab host runs the command. Unless noted, server steps run on vm1.lab.example and client steps run on vm2.lab.example.
What the keepalive directives do
From the OpenSSH manuals:
ClientAliveInterval(server): after this many seconds with no data from the client,sshdsends an encrypted keepalive request. Default0disables those probes.ClientAliveCountMax(server): how many consecutive probes may go unanswered beforesshdcloses the session.0disables termination from this mechanism — it does not force a faster disconnect.ServerAliveInterval(client): thesshclient sends its own keepalive traffic when the session is quiet. Use this when a middlebox drops idle TCP flows even though both sides are healthy.ServerAliveCountMax(client): how many unanswered client keepalive cycles occur beforesshgives up.TCPKeepAlive(server): enables or disables OS-level TCP keepalive probes. Separate fromClientAlive*; useful for detecting some dead peers but not a substitute for encrypted SSH keepalives.
For a keyboard-idle but otherwise healthy session, the OpenSSH client normally answers server ClientAlive probes automatically. OpenSSH documents the configured disconnect delay as approximately ClientAliveInterval × ClientAliveCountMax after the connection becomes unresponsive; observed wall-clock time can vary depending on when the client last sent data and on scheduling.
If your goal is to close an SSH shell because its channel has no traffic, recent OpenSSH versions also provide ChannelTimeout. That measures inactivity on an SSH channel, not merely keyboard silence in every case — activity on the relevant channel resets its timeout, and it is separate from connection-level keepalive detection with ClientAlive*. See ChannelTimeout in sshd_config(5).
Read effective SSH server values
Before you change timeouts, read what sshd actually runs. Drop-in files under /etc/ssh/sshd_config.d/ override the main file.
On the server (vm1.lab.example), print the merged configuration:
sudo sshd -T | grep -iE 'clientalive|tcpkeepalive|channeltimeout'Sample output on a fresh RHEL 10 install before this lab adds overrides:
clientaliveinterval 0
clientalivecountmax 3
tcpkeepalive yes
channeltimeout noneclientaliveinterval 0 means the server is not actively probing idle clients yet. tcpkeepalive yes is the RHEL default at the TCP layer.
Drop unresponsive clients on the server
When you want sshd to close sessions whose client stops answering keepalive probes — crashed laptop, suspended process, or one-way network failure — set ClientAliveInterval and a non-zero ClientAliveCountMax on the server.
Create a drop-in on vm1.lab.example (adjust seconds for production; short values are easier to test):
# /etc/ssh/sshd_config.d/99-client-alive.conf
ClientAliveInterval 15
ClientAliveCountMax 1Validate syntax and reload sshd without dropping existing sessions:
sudo sshd -tThat command exits silently when the file parses cleanly.
Reload the running daemon:
sudo systemctl reload sshdConfirm the new values are active:
sudo sshd -T | grep -i clientaliveSample output:
clientaliveinterval 15
clientalivecountmax 1OpenSSH documents the timeout as approximately ClientAliveInterval × ClientAliveCountMax. With 15 and 1, expect roughly 15 seconds after the connection becomes unresponsive — not keyboard-idle, but a client that stops answering keepalive probes. Observed timing in a test can vary depending on when the connection last exchanged data.
A responsive client stays connected while idle
From vm2.lab.example, start a quiet remote command — no typing, but the client still answers server probes:
ssh root@192.168.56.116 sleep 120That session stayed up for the full two minutes in this lab even with ClientAliveInterval 15. A user who stops typing is not necessarily an unresponsive SSH client.
If the client stops responding because its process, host, or network path fails, sshd eventually disconnects it according to ClientAliveInterval and ClientAliveCountMax. Use list active SSH connections (who, w, or ss -tnp sport = :22) on the server while you test policy changes.
Keep idle sessions alive from the client
When the problem is a NAT router, VPN concentrator, or firewall that forgets quiet TCP flows, configure keepalives on the SSH client — the machine where you run ssh.
On vm2.lab.example, scope the settings to one server in ~/.ssh/config:
Host vm1.lab.example 192.168.56.116
ServerAliveInterval 60
ServerAliveCountMax 3Ask OpenSSH to print the merged client options for that host:
ssh -G vm1.lab.example | grep -i aliveSample output:
serveraliveinterval 60
serveralivecountmax 3
tcpkeepalive yesAfter 60 seconds without receiving data from the server, the client sends an encrypted keepalive request. After three failed rounds it exits — long enough to survive most home-router idle timers without hammering the server every few seconds.
This does not change sshd on the remote host. It only affects connections initiated from hosts that use this client configuration.
TCPKeepAlive on the server
TCPKeepAlive in /etc/ssh/sshd_config toggles whether the kernel sends TCP-level keepalive probes on SSH connections. On this lab host the merged server config already showed tcpkeepalive yes in the earlier sshd -T step.
TCPKeepAlive is enabled by default in this tested configuration. It uses kernel TCP keepalives and is separate from the encrypted ClientAlive* / ServerAlive* mechanisms. You normally do not need to change it just to configure the SSH keepalive behavior covered here.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Keyboard-idle users stay connected | Expected behavior with ClientAlive*; healthy clients answer probes |
Use ChannelTimeout or shell-level idle timeout if you need inactivity-based logout |
| Dead or unresponsive clients stay connected | ClientAliveInterval 0, ClientAliveCountMax 0, or probes disabled |
Set a positive ClientAliveInterval and a positive ClientAliveCountMax on the server, then reload sshd |
| Sessions still die on a quiet laptop | NAT or firewall idle timeout | Add ServerAliveInterval on the client ssh configuration |
Changed sshd_config but old timing persists |
Typo or missing reload | Run sudo sshd -t, then sudo systemctl reload sshd, then re-check with sudo sshd -T |
| Directive seems ignored | Edited the wrong side | ClientAlive* on server; ServerAlive* on client |
sshd -t fails after edit |
Invalid keyword or unit in drop-in | Fix the drop-in file; do not reload until sshd -t succeeds |
References
- sshd_config(5) — OpenSSH daemon configuration file
- ssh_config(5) — OpenSSH client configuration file
- Red Hat Enterprise Linux 10 — Secure communication with OpenSSH
Summary
Idle SSH timeouts depend on which side of the connection you configure. On the server (vm1.lab.example in this lab), ClientAliveInterval and a positive ClientAliveCountMax make sshd probe the client and drop sessions that stop answering — but a normal OpenSSH client still answers those probes when you simply stop typing. OpenSSH describes that mechanism as detecting unresponsive connections; ClientAliveCountMax 0 disables termination from it. For channel-level shell inactivity, see ChannelTimeout in the upstream manual.
On the client (vm2.lab.example), ServerAliveInterval and ServerAliveCountMax keep quiet TCP flows alive through NAT and stateful firewalls. TCPKeepAlive is a separate kernel-level option on the server and is not the same knob as the encrypted keepalive settings in this walkthrough.
Always verify merged settings with sudo sshd -T on the server and ssh -G host on the client before you assume a timeout change took effect. For stuck interactive sessions that ignore keepalives, see kill or disconnect a hung SSH session instead of tuning idle timers alone.

