Debugging SSH “kex_exchange_identification: Connection closed by remote host” Error in Public-Facing Servers


2 views

When analyzing SSH server logs, the error: kex_exchange_identification: Connection closed by remote host typically occurs during the initial key exchange phase, before authentication begins. This differs from Connection refused or authentication failures, indicating the remote client terminated the connection during protocol negotiation.

# Network-level causes
- Port scanning tools (like masscan/zmap) closing connections immediately
- Botnet probes failing protocol version checks
- Network timeouts during initial handshake

# Configuration-related causes
- Overly restrictive KEX/Cipher/MAC settings
- Protocol version mismatches
- TCP wrappers/firewalls interrupting mid-connection
- Resource exhaustion (though MaxStartups shows differently)

Your sshd_config shows robust security settings, but several elements warrant attention:

KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MaxStartups 3:100:60

To properly debug, increase logging granularity:

# Temporarily modify sshd_config
LogLevel DEBUG3
# Then monitor connections
sudo tcpdump -i eth0 -nn 'port 22' -w ssh_capture.pcap

Option 1: Protocol Filtering
Add to sshd_config:

# Reject clients that don't speak proper SSH
Match Address *,!your_trusted_ip
    MaxSessions 0
    MaxStartups 1:100:30
    LoginGraceTime 30s

Option 2: Connection Throttling
Implement fail2ban with custom filters:

# /etc/fail2ban/filter.d/sshd_preauth.conf
[Definition]
failregex = ^%(__prefix_line)serror: kex_exchange_identification: Connection closed by .* $$preauth$$$
ignoreregex =

For high-volume servers, consider this optimized setup:

# /etc/ssh/sshd_config.d/99-hardening.conf
Port 22
Protocol 2
ListenAddress 0.0.0.0
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
KexAlgorithms curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 2
MaxSessions 3
MaxStartups 5:100:60
UseDNS no
TCPKeepAlive yes
Compression delayed

Create a real-time monitoring script:

#!/bin/bash
# Monitor SSH preauth disconnects
journalctl -u sshd -f | grep --line-buffered \
    -e "kex_exchange_identification" \
    -e "Connection closed.*preauth" \
    | while read line; do
        echo "$(date) - $line" >> /var/log/ssh_abnormal.log
        # Optional: trigger firewall rule
        ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
        [ -n "$ip" ] && iptables -A INPUT -s "$ip" -j DROP
    done

The kex_exchange_identification: Connection closed by remote host error occurs during the initial key exchange phase of SSH connection establishment. This is different from authentication failures or connection drops due to rate limiting.

# Typical log sequence showing the failure:
Apr 20 03:59:36 myhostname sshd[22186]: Connection from x.x.x.83 port 34876
Apr 20 03:59:36 myhostname sshd[22186]: error: kex_exchange_identification: Connection closed by remote host

1. Protocol Incompatibility: When the client's SSH version string doesn't match server expectations. This often happens with:

  • Malformed version strings from scanners
  • Legacy clients with outdated protocols

2. TCP Connection Issues: The remote host terminates the connection before completing key exchange, which could indicate:

  • Network middleboxes (firewalls, IDS) interrupting the handshake
  • Client-side timeout configurations

Your current config shows good security practices but could be adjusted:

# Recommended additions to sshd_config:
Match Address *,!trusted_ips
    MaxStartups 3:100:60
    LoginGraceTime 30
    ClientAliveInterval 15
    ClientAliveCountMax 3

1. Packet Capture Analysis:

sudo tcpdump -i eth0 -w ssh.pcap port 22
# Filter for failed connections in Wireshark using:
# tcp.flags.reset == 1 || tcp.flags.fin == 1

2. Enhanced Logging:

# Temporarily increase verbosity:
LogLevel DEBUG3
# Then monitor with:
tail -f /var/log/auth.log | grep --line-buffered "kex_exchange"

For a server experiencing frequent scanner connections:

# /etc/ssh/sshd_config.d/rate_limit.conf
MaxStartups 10:30:60
MaxSessions 10
TCPKeepAlive yes
UseDNS no

To automatically block repeat offenders:

# /etc/fail2ban/jail.d/ssh-kex.conf
[sshd-kex]
enabled = true
filter = sshd-kex
logpath = /var/log/auth.log
maxretry = 3
findtime = 1h
bantime = 24h

After changes, always verify syntax:

sudo sshd -t && sudo systemctl restart sshd