Connection closed by 172.22.0.3 port 8080, with
kex_exchange_identification: Connection closed by remote host above it when you
add -v, appeared here when ssh was pointed at a port where a web
server listened, not an SSH server. Check the port: connect to the port where
sshd listens.
The -v output shows what answered instead of SSH: in this test, an HTML error
page from Python's http.server.
The error
$ ssh -o BatchMode=yes -p 8080 dev@web true
Connection closed by 172.22.0.3 port 8080
exit=255
Why it happens
Port 8080 on web ran python3 -m http.server. The plain message says
only that the other side closed the connection. With -v, the client showed
every line it received while waiting for the server's SSH identification line, and they were
HTML (the command filters out the configuration lines at the top; the cut is marked):
$ ssh -v -o BatchMode=yes -p 8080 dev@web true 2>&1 | grep -v -E '^debug1: (Reading|/etc/ssh|identity file|Local version|Authenticator|OpenSSH)'
OpenSSH_9.6p1 Ubuntu-3ubuntu13.20, OpenSSL 3.0.13 30 Jan 2024
debug1: Connecting to web [172.22.0.3] port 8080.
debug1: Connection established.
debug1: kex_exchange_identification: banner line 0: <!DOCTYPE HTML>
...
debug1: kex_exchange_identification: banner line 8: <p>Error code: 400</p>
debug1: kex_exchange_identification: banner line 9: <p>Message: Bad HTTP/0.9 request type ('SSH-2.0-OpenSSH_9.6p1').</p>
...
kex_exchange_identification: Connection closed by remote host
Connection closed by 172.22.0.3 port 8080
exit=0
The connection itself was established, as the third line shows, so the host and port were
reachable. The web server read the client's own identification line, SSH-2.0-OpenSSH_9.6p1,
as an HTTP request, answered with a 400 error page and closed the connection. (The
exit=0 in this block is the exit code of grep, the last command of
the pipeline; the unfiltered run above exited with 255.)
The fix
Connect to the SSH port. In this test the SSH server was the container sshd on
port 22, and the login worked there, while port 8080 answered HTTP as it should:
$ ssh -o BatchMode=yes sshd hostname
9aec0d80cdf9
exit=0
$ curl -sS -o /dev/null -w '%{http_code}\n' http://web:8080/
200
exit=0
The user dev for that login came from the client's ~/.ssh/config. A
"banner exchange ... invalid format" message did not appear in this test;
this OpenSSH version printed the two lines above. Other causes of a closed connection at
this step were not tested.
How it was reproduced
Reproduced inside ubuntu:24.04 containers on one user-defined Docker network on
Docker engine 29.9.3, with OpenSSH_9.6p1 Ubuntu-3ubuntu13.20 in the client. The container
web ran Python 3.12.3's http.server on 8080; the container
sshd ran OpenSSH's server on 22 with a test user dev. The
containers and the network were removed afterwards.
Frequently asked
- What does kex_exchange_identification: Connection closed by remote host mean?
- The server closed the connection before it sent an SSH identification line. In this test the port belonged to a web server, which answered with an HTML error page.
- How do I see what answered on an SSH port?
- Run ssh with -v. The kex_exchange_identification: banner line entries show each line the other side sent; here they were HTML.
- How do I fix Connection closed by port when using ssh?
- In this test the fix was to connect to the port where sshd listened. Port 8080 was a web server and answered HTTP, not SSH.
When nothing at all listens on the port, the message is different: SSH: connect to host port 22: Connection refused. More decoded errors in the Fixes category.