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