Resolving SSH Connection Timeout on Ubuntu 20.04 LTS (Port 22 & Client Keepalive)

Fix SSH connection timeouts on Ubuntu 20.04. Configure client-side KeepAlive settings and troubleshoot network issues for stable SSH sessions.


Fix SSH connection timeouts on Ubuntu 20.04. Configure client-side KeepAlive settings and troubleshoot network issues for stable SSH sessions.

When managing remote servers, an SSH connection timeout can be a significant productivity hindrance, disrupting ongoing work and requiring constant re-authentication. This guide addresses common causes and provides a comprehensive, technical resolution for SSH connection timeouts on Ubuntu 20.04 LTS, focusing specifically on port 22 and the critical role of client-side keepalive configurations, along with essential network diagnostics.

Symptom & Error Signature

Users typically experience a frozen terminal session, followed by a disconnection. Attempting to initiate a new SSH session immediately after a dropout often yields the following error signature:

$ ssh user@your_server_ip
ssh: connect to host your_server_ip port 22: Connection timed out

Or, an existing session might simply hang for an extended period, eventually displaying:

Packet_write_wait: Connection to your_server_ip port 22: Broken pipe

These symptoms indicate that the SSH client attempted to establish or maintain a connection over TCP port 22 but did not receive a timely response from the remote SSH server, or the connection was forcibly closed by an intermediary device or the server itself due to inactivity.

Root Cause Analysis

An SSH connection timeout, particularly when port 22 is explicitly mentioned, often stems from a combination of network conditions and configuration settings. Understanding the underlying reasons is crucial for effective troubleshooting:

  1. Network Latency & Instability: The most frequent culprit. High latency, packet loss, or transient network instability between your client and the server can cause TCP sessions to drop if acknowledgements aren't received within expected timeframes. This can be exacerbated by overly aggressive firewall rules or NAT timeouts on intermediate network devices (routers, firewalls, load balancers, ISP equipment).

  2. Client-Side Inactivity (Lack of Keepalives): By default, SSH clients may not send periodic "keepalive" packets to maintain a connection when idle. If there's no data transfer for an extended period, firewalls or NAT devices along the network path might deem the connection inactive and tear it down to free up resources, leading to a timeout. This is where client-side ServerAliveInterval and ServerAliveCountMax become essential.

  3. Server-Side Inactivity (ClientAlive settings): While the focus is on client config, the SSH server (sshd) also has its own keepalive mechanisms (ClientAliveInterval, ClientAliveCountMax). If the server is configured to aggressively disconnect idle clients and the client isn't sending its own keepalives, the server will terminate the connection.

  4. Firewall Restrictions:

    • Client-side Firewall: A local firewall (e.g., UFW on your Ubuntu workstation) might be blocking outgoing connections on port 22.
    • Server-side Firewall: The server's firewall (e.g., UFW, iptables, cloud provider security groups) might be misconfigured, blocking incoming connections on port 22, or suddenly dropping established connections.
    • Intermediate Firewalls: Corporate firewalls, ISP firewalls, or cloud network security appliances might be inspecting traffic and terminating long-lived or idle connections.
  5. DNS Resolution Issues: Although less common for explicit "Connection timed out" after an initial connection attempt, slow or failed DNS resolution of the hostname can delay the connection process, potentially leading to a timeout.

  6. MTU Mismatch: Maximum Transmission Unit (MTU) mismatches between network devices can cause packet fragmentation or loss, leading to unresponsive connections that eventually time out. This is a more subtle issue, often manifesting as connections that initially work but then hang.

Step-by-Step Resolution

Follow these steps to diagnose and resolve SSH connection timeouts on your Ubuntu 20.04 LTS system.

1. Initial Network Diagnostics (Client-Side)

Before modifying SSH configurations, verify basic network connectivity to your server.

a. Ping the Server: Check for basic reachability and latency.

```bash
ping -c 5 your_server_ip_or_hostname
```

High packet loss or very high latency indicates a fundamental network issue that needs to be addressed before focusing on SSH config.

b. Check Port Reachability with nc or telnet: Verify that TCP port 22 is open and reachable on the server.

```bash
# Using netcat (nc)
nc -vz your_server_ip 22

# Expected successful output:
# Connection to your_server_ip 22 port [tcp/ssh] succeeded!

# Using telnet (install if not present: sudo apt install telnet)
telnet your_server_ip 22

# Expected successful output:
# Trying your_server_ip...
# Connected to your_server_ip.
# Escape character is '^]'.
# SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5
# (Press Ctrl+] then type 'quit' and Enter to exit telnet)
```
If `nc` or `telnet` fail, the problem is likely a firewall blocking port 22 on the server side or an intermediary network device.

c. Trace the Route with traceroute or mtr: Identify where packets might be getting dropped or experiencing high latency. mtr (My Traceroute) provides a continuous, more detailed view.

```bash
# Install mtr if not present
sudo apt install mtr

# Trace the route
mtr your_server_ip_or_hostname
# (Press 'q' to quit mtr)
```
Look for high packet loss or long response times at specific hops. This can point to an issue with your ISP, an intermediate router, or the server's network provider.

2. Configure Client-Side SSH Keepalives

This is the primary client-side solution for preventing timeouts due to inactivity. You can configure this either globally or per-host.

a. Edit ~/.ssh/config (Per-User/Per-Host): This is the recommended approach as it applies only to your user and can be customized per server. Create or edit the file ~/.ssh/config.

```bash
nano ~/.ssh/config
```

Add or modify the following lines for specific hosts or globally:

```config
Host your_server_alias_or_ip
    Hostname your_server_ip_or_hostname
    User your_username
    ServerAliveInterval 60
    ServerAliveCountMax 3

# Or for all hosts (place at the beginning of the file)
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
```

*   **`ServerAliveInterval 60`**: This instructs the SSH client to send a null packet to the server every 60 seconds if no data has been received from the server. This keeps the TCP connection alive, preventing idle timeouts from firewalls or NAT devices.
*   **`ServerAliveCountMax 3`**: If the client sends `ServerAliveInterval` packets and receives no response for `ServerAliveCountMax` consecutive times, it will disconnect and output "Broken pipe". In this example, it will try for 3 * 60 = 180 seconds (3 minutes) before giving up.

> [!IMPORTANT]
> The `Host *` configuration applies to all SSH connections you make from your client. If you have specific servers with unique requirements, define them individually *before* the `Host *` block in your `~/.ssh/config` file.

b. Edit /etc/ssh/ssh_config (System-Wide): This file affects all users on your client machine. Only modify this if you need a global setting for all users.

```bash
sudo nano /etc/ssh/ssh_config
```

Uncomment or add the following lines:

```config
Host *
    SendEnv LANG LC_*
    HashKnownHosts yes
    GSSAPIAuthentication yes
    GSSAPIDelegateCredentials no
    ServerAliveInterval 60
    ServerAliveCountMax 3
```

Save the file and exit. No service restart is required for client-side configuration changes. Test your SSH connection.

3. Verify Server-Side SSH Daemon Configuration (Optional, but Good Practice)

While the issue is described as client-side, an overly aggressive server-side configuration can effectively negate client-side keepalives if the server closes the connection before the client has a chance to send one.

a. Check sshd_config on the Server: SSH into your server (you might need to be quick before it times out, or use the temporary client-side keepalive config from step 2).

```bash
sudo nano /etc/ssh/sshd_config
```

Look for `ClientAliveInterval` and `ClientAliveCountMax`. If they are uncommented and set to very low values, they could be contributing.

```config
# Example default or appropriate settings
# ClientAliveInterval 0 # (0 disables this feature)
# ClientAliveCountMax 3
```

If `ClientAliveInterval` is set to `0`, the server will not send keepalives and rely entirely on the client or application-layer traffic. If it's a positive value, ensure it's not too short (e.g., less than 30 seconds unless specific requirements dictate). Generally, leaving `ClientAliveInterval` at `0` on the server and relying on client-side `ServerAliveInterval` is common.

> [!WARNING]
> Misconfiguring `sshd_config` can lock you out of your server. Always have an alternative access method (e.g., KVM console, cloud provider serial console) before making critical changes.

b. Restart SSH Daemon on the Server: If you make changes to /etc/ssh/sshd_config, you must restart the SSH service for them to take effect.

```bash
sudo systemctl restart sshd
```

4. Check Firewall Rules (UFW/IPTables)

Ensure that firewalls on both your client and the server are not blocking SSH traffic.

a. Client-Side Firewall (If Applicable): If you're running a firewall on your local Ubuntu workstation (e.g., UFW), ensure it allows outgoing connections on port 22.

```bash
sudo ufw status verbose
```
If UFW is enabled and blocking, you might need to allow outgoing connections:
```bash
sudo ufw allow out 22/tcp
```

b. Server-Side Firewall (UFW/Cloud Security Groups): Ensure the server's firewall is correctly configured to allow incoming connections on port 22.

```bash
sudo ufw status verbose
```
If port 22 is not allowed:
```bash
sudo ufw allow OpenSSH
# Or for specific port
sudo ufw allow 22/tcp
sudo ufw enable # if not already enabled
sudo ufw status verbose
```
If you are using a cloud provider (AWS Security Groups, Azure Network Security Groups, Google Cloud Firewall Rules), verify that these are also configured to allow inbound TCP traffic on port 22 from your client's IP address range.

5. MTU Issues (Advanced Troubleshooting)

An MTU mismatch can cause packets to be dropped silently, leading to connection hangs and timeouts. This is less common but worth investigating if other solutions fail.

a. Identify Current MTU: On both client and server, check the MTU of your primary network interface.

```bash
ip link show eth0 # Replace eth0 with your active interface (e.g., enp0s3, ens18)
```

Look for `mtu 1500` (common for Ethernet) or `mtu 1400` (common for VPNs or some cloud environments).

b. Test for MTU Problems with ping: Try sending large, non-fragmented packets.

```bash
# From client to server
ping -M do -s 1472 your_server_ip
```
(1472 bytes + 28 bytes IP/ICMP header = 1500 byte packet).
If you see "Frag needed and DF set" or packet loss, try decreasing the `-s` value until packets go through. For example, if 1472 fails, try 1400.

c. Adjust MTU (If Necessary): If you identify an MTU issue, you might need to adjust the MTU on one or both ends. For example, to set MTU to 1400 on the eth0 interface:

```bash
sudo ip link set dev eth0 mtu 1400
```
This change is temporary. For persistence, modify your network configuration file (e.g., `/etc/netplan/*.yaml` on Ubuntu 20.04) or systemd-networkd configuration.

6. DNS Resolution Verification

While less direct for "Connection timed out" on port 22, ensuring proper DNS resolution can prevent delays.

a. Test DNS Resolution:

```bash
dig your_server_hostname
```
Ensure it resolves quickly and correctly to your server's IP address. If `dig` fails or is slow, check your client's `/etc/resolv.conf` and the server's DNS configuration.

By systematically working through these steps, you should be able to identify and resolve the root cause of your SSH connection timeout issues on Ubuntu 20.04 LTS, ensuring stable and reliable remote access.