The Git server closed the SSH connection during the initial identification/banner exchange. On GitHub this is almost always IP-based rate limiting from a shared or NAT'd address, but blocked SSH ports can also cause it.
`kex_exchange_identification: Connection closed by remote host` means the SSH server dropped your connection during the very first stage of the handshake — before key exchange completes and before any authentication happens. The server (or something in front of it) sent a TCP close instead of an SSH protocol banner. Because this failure occurs before authentication, it is not caused by a missing or misconfigured SSH key, and keep-alive settings cannot prevent it (there is no established session yet to keep alive). Against the large hosted providers (GitHub, GitLab, Bitbucket), the most common reason by far is server-side rate limiting / abuse throttling tied to your public IP address. Many users behind the same NAT gateway, VPN exit node, corporate proxy, or cloud/CI provider share one outbound IP, and the combined connection rate can trip the provider's protection, which then briefly refuses new SSH connections from that IP. GitHub explicitly documents this behavior for `kex_exchange_identification: Connection closed by remote host`. Other causes are connectivity-related: a firewall or proxy blocking outbound TCP port 22, the SSH endpoint being temporarily unreachable, or — for self-hosted servers you control — `sshd` rejecting the connection early (for example via `MaxStartups` throttling or resource exhaustion).
Run a verbose SSH test so you can see exactly where the connection dies:
ssh -vT [email protected]If the output ends shortly after Connecting to github.com ... with kex_exchange_identification: Connection closed by remote host — before any line about offering a public key or a passphrase prompt — then the server closed the connection during the initial handshake. This rules out SSH key problems and idle-timeout problems, and points to rate limiting or a network block.
Because the dominant cause on hosted providers is per-IP rate limiting, the simplest and most effective fix is to wait about a minute and try again:
sleep 60 && ssh -T [email protected]If you are hitting this from CI or a script that opens many Git connections rapidly, lower the rate instead of hammering the server:
- Avoid running many parallel git fetch/git clone jobs that all share one egress IP.
- Add a short backoff-and-retry around Git steps rather than retrying instantly.
- Reuse a single SSH connection for multiple Git operations with connection multiplexing in ~/.ssh/config:
Host github.com
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 60sIf an entire office or many CI jobs share one NAT'd public IP, the combined rate can trip the limit even when your own usage is light — in that case the network's shared IP is the real culprit (see step 4).
If retries never succeed, your network may be blocking SSH. Test raw TCP reachability to port 22:
nc -vz github.com 22
# or
ssh -vT [email protected]A connection that never establishes (rather than one that establishes and is then closed) points to a firewall/proxy blocking port 22.
GitHub (and some other providers) expose SSH on port 443, which is rarely blocked. Test it:
ssh -T -p 443 [email protected]If that succeeds, make it permanent in ~/.ssh/config:
Host github.com
Hostname ssh.github.com
Port 443
User gitNote that switching networks (sharing a different egress IP) can also be what gets you past a rate limit, not only the port change.
A misconfigured local SSH proxy or a VPN can sever the connection before the banner is exchanged. Inspect your effective SSH settings:
ssh -G github.com | grep -i -E 'proxy|hostname|port'If a ProxyCommand or ProxyJump is set that you do not expect, comment it out and retry. If you are on a VPN, temporarily disconnect and test again:
ssh -T [email protected]If it works without the VPN, the VPN's exit IP is likely rate limited or its routing/inspection is dropping the connection — use split tunneling to send Git traffic outside the VPN, or use HTTPS for Git.
HTTPS uses port 443 and a separate path that is not subject to the same SSH handshake throttling, so it is a reliable workaround:
# See current remotes
git remote -v
# Switch this repo's remote to HTTPS
git remote set-url origin https://github.com/<owner>/<repo>.gitAuthenticate with a personal access token (not a password). Use a credential helper that stores credentials securely for your platform rather than plaintext, for example:
# macOS
git config --global credential.helper osxkeychain
# Windows
git config --global credential.helper manager
# Linux (libsecret-backed)
git config --global credential.helper libsecretFor a self-hosted Git/SSH server you control, the early close usually comes from connection throttling or resource limits. Check the auth logs for the rejection reason:
sudo journalctl -u ssh -f # systemd
sudo tail -f /var/log/auth.log # Debian/Ubuntu
sudo tail -f /var/log/secure # RHEL/CentOSIf you see connections being dropped before authentication under load, review MaxStartups and MaxSessions in /etc/ssh/sshd_config. The default MaxStartups 10:30:100 starts randomly dropping unauthenticated connections once 10 are pending. Raise it cautiously if legitimate concurrent traffic is being throttled, then reload:
sudo sshd -t && sudo systemctl reload sshAlso confirm the host is not out of memory or file descriptors, which can cause sshd to close connections early:
free -h
ss -sWhy keep-alive does not help: ServerAliveInterval/ServerAliveCountMax only affect an already-established SSH session by sending keepalive packets to prevent idle disconnects. The kex_exchange_identification: Connection closed by remote host error happens before the session exists, so these settings have no effect on it.
Why this is not a host-key problem: the error appears before host-key verification, so editing or wiping ~/.ssh/known_hosts will not fix it (a genuine host-key change reports a different, explicit warning). Avoid bulk-deleting known_hosts — that removes verification for every host you trust and invites man-in-the-middle attacks. If you ever must remove a single stale entry, scope it precisely: ssh-keygen -R github.com.
CI/CD specifics: hosted runners (GitHub-hosted, GitLab shared runners, etc.) and cloud egress gateways share IP pools across many tenants, so you can be throttled by activity that is not yours. Mitigations: cache dependencies and avoid re-cloning, reuse a single SSH connection via ControlMaster/ControlPersist, add bounded exponential backoff with retries around clone/fetch, prefer shallow clones (--depth=1) where history is not needed, and consider HTTPS with a token for fetch-only steps.
Corporate networks: security appliances doing deep packet inspection or TLS/SSH interception can terminate connections during the handshake. The correct fix is to have your network team allowlist the provider's SSH endpoints or permit SSH-over-443 — not to disable any verification.
ssh: Could not resolve hostname github.com: Name or service not known
How to fix 'ssh: Could not resolve hostname github.com: Name or service not known' in Git
error: insufficient permission for adding an object to repository database .git/objects
How to fix "insufficient permission for adding an object to repository database" in Git
fatal: could not create work tree dir 'repo': Permission denied
How to fix "could not create work tree dir: Permission denied" in Git
Smudge error: Error downloading object: The requested URL returned error
How to fix Git LFS 'Smudge error: Error downloading object' error
fetch-pack: unexpected disconnect while reading sideband packet
How to fix 'unexpected disconnect while reading sideband packet' in Git