Why My SSH Session Drops After a Few Minutes (and How to Fix It)

If your SSH session to a VPS keeps disconnecting after a few minutes, the most common culprit is idle timeouts imposed by NAT devices, firewalls, or server policies. Here’s a concise, copy-pasteable guide to fix it from both the client and server sides, plus a few sneaky “gotchas” to check.


TL;DR (Quick Fix)

Client (OpenSSH):

ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3 user@your-vps

Server (Debian/Ubuntu):

sudo nano /etc/ssh/sshd_config
# Add or adjust:
ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes

sudo systemctl reload sshd

That alone fixes it for most people.


1) Client-Side Keepalives (Immediate Relief)

OpenSSH (macOS/Linux/Windows PowerShell)

One-off:

ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3 user@your-vps

Persistent (recommended): create or edit ~/.ssh/config:

Host vps
    HostName your-vps-domain-or-ip
    User user
    ServerAliveInterval 30
    ServerAliveCountMax 3

Then connect with:

ssh vps

PuTTY

  • Connection → Seconds between keepalives: 30
  • Enable TCP keepalives (SO_KEEPALIVE): checked
    Save the session so you don’t have to redo this.

2) Server-Side Keepalives (Make It Stick)

On your VPS (Debian/Ubuntu):

sudo nano /etc/ssh/sshd_config

Ensure the following lines exist (add or modify as needed—avoid duplicates):

ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes
# Optional: LoginGraceTime 120

Reload without dropping current sessions:

sudo systemctl reload sshd   # or: sudo systemctl reload ssh

What this does: the server proactively pings the client so middleboxes don’t reclaim the “idle” TCP session.


3) Hidden Killers to Check

A) Shell Auto-Logout via TMOUT

If TMOUT is set, the shell will auto-logout after N seconds.

Check:

echo $TMOUT

If you see a number (e.g., 300), disable it:

unset TMOUT

Then remove or comment any TMOUT=... lines in:

  • /etc/profile
  • /etc/bash.bashrc
  • ~/.bashrc
  • ~/.zshrc (if using zsh)

B) Fail2Ban / SSHGuard False Positives

If you had multiple failed logins from the same IP/device, you might be getting briefly banned.

Inspect:

sudo journalctl -u ssh -e
sudo fail2ban-client status sshd     # if Fail2Ban is installed

If banned, fix the offending behavior and consider allowlisting your trusted IP.

C) VPN / NAT / Firewall Idle Timeouts

Routers and corporate networks often drop idle TCP sessions.

  • Keepalives (client/server) usually solve this.
  • Using WireGuard? Add a persistent keepalive:

Apply changes:

sudo wg-quick down wg0 && sudo wg-quick up wg0

On the client side, in [Peer] section:

PersistentKeepalive = 25

4) Troubleshooting Playbook (When It Still Drops)

Watch SSH logs live (on the server) while you reproduce the issue:

sudo journalctl -u ssh -f

Check for kernel/network hiccups:

dmesg --ctime | tail -n 100

Test path stability from the client (packet loss, jitter):

# On macOS: brew install mtr   | On Debian/Ubuntu: sudo apt install mtr-tiny
mtr -rwzbc100 your-vps-ip

Interpretation basics:

  • Loss% > 0 at the last hop? Likely network flakiness or aggressive middlebox.
  • High jitter? Keepalives help; consider reducing intervals (e.g., 20–30s).

5) Suggested Defaults (Safe and Sane)

Client (~/.ssh/config):

Host *
    ServerAliveInterval 30
    ServerAliveCountMax 3
    TCPKeepAlive yes

Server (/etc/ssh/sshd_config):

ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes

Why these values?

  • ServerAliveInterval 30 keeps the pipe warm through cranky NATs.
  • Server-side 60s is conservative and reduces noise if many clients connect.
  • CountMax 3 gives transient blips ~1.5–3 minutes to recover before disconnect.

6) FAQ

Q: Does TCPKeepAlive yes duplicate ServerAliveInterval?
A: They’re different. TCPKeepAlive uses kernel-level keepalives (slow, infrequent by default). ServerAliveInterval is an SSH-level heartbeat—more reliable across NAT/firewalls. Use both.

Q: My session drops exactly at X minutes.
A: That screams policy/timeout (router, FW, corporate proxy, cloud LB). Lower your keepalive interval (e.g., 15–30s) and set server keepalives too.

Q: I’m using a jump host / ProxyJump.
A: Add keepalives on the client and ensure the jump host’s sshd_config has ClientAlive* set. Every hop matters.


One-Line Summary

Most “SSH disconnects after a few minutes” are idle timeouts. Add client keepalives (e.g., ServerAliveInterval=30) and server keepalives (ClientAliveInterval=60), then rule out TMOUT, Fail2Ban bans, and VPN/NAT timeouts. That’s it—rock-solid sessions.