ssh -vv で読む接続の5段階を実測
ssh が繋がらないとき、最初に決めたいのは鍵の問題なのかネットワークの問題なのかです。この2つは対処が正反対で、取り違えると何時間も溶けます。
SSH で「鍵」と呼ばれるものは3つあります。ログインに使う自分の鍵 (ユーザー鍵。秘密鍵と公開鍵の対)、サーバーが本物であることを示すホスト鍵、通信を暗号化する使い捨ての鍵を両者で作る鍵交換です。ここで言う「鍵の問題」は1つ目のユーザー鍵の話です。

見分け方は単純で、ssh -vv の出力が5段階のどこで止まったかを見ます。4段階目までに止まっていたら、自分の秘密鍵はまだ一度も使われていません。 つまりユーザー鍵は無関係です。
OpenSSH 10.0p2 のクライアントから同じ 10.0p2 のサーバーに入ったときの実際のログを、段階ごとに切って読みます。
段階ごとに何が起きているか
debug1: Connecting to lab-target [172.19.0.2] port 22.
debug1: Local version string SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
debug1: Remote protocol version 2.0, remote software version OpenSSH_10.0p2 Debian-7+deb13u4
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: mlkem768x25519-sha256
debug1: kex: host key algorithm: ssh-ed25519
debug1: Server host key: ssh-ed25519 SHA256:3kstJR8PODEpAfBSzXcvsukN+wNfIZtHhG55W0a7o84
debug1: Authentications that can continue: publickey,password
この9行が5段階です。
| 段階 | 目印になる行 | やっていること |
|---|---|---|
| 1 TCP接続 | Connecting to ... port 22 | 名前解決してTCPを張る |
| 2 バージョン交換 | Local version string / Remote protocol version | 平文でお互いの実装と版を名乗る |
| 3 アルゴリズム交渉 | SSH2_MSG_KEXINIT sent / received | 使える暗号方式の一覧を送り合う |
| 4 鍵交換とホスト鍵検証 | kex: algorithm: / Server host key: | 共通鍵を作り、相手が本物かを確かめる |
| 5 ユーザー認証 | Authentications that can continue: | ここではじめて鍵やパスワードを出す |

(電話にたとえると、1〜4段階はつながって名乗り合い、相手が本人かを確かめるところまでで、自分が誰かを証明する (秘密鍵を使う) のは5段階目です)
2段階目までは平文です。 バージョン文字列はまだ暗号化されていないので、nc でポートを叩くだけでも読めます。
$ head -c 40 < /dev/tcp/127.0.0.1/2222
SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
サーバーのOpenSSHの版がここで分かることは、Tailscale越しのSSHとMTU 1280の章で効いてきます。交渉される鍵交換方式は両端の版で決まるからです。
止まった位置から原因を引く
| 最終行 | 段階 | 疑う先 |
|---|---|---|
Connecting to ... のまま | 1 | 経路・ファイアウォール・そもそもsshdが動いていない |
Remote protocol version まで | 2 | 稀。sshd が接続直後に落ちている |
SSH2_MSG_KEXINIT sent まで | 3 | 送信方向のパケットが届いていない |
expecting SSH2_MSG_KEX_ECDH_REPLY | 4 | ユーザー鍵より経路を疑う。 パケットサイズとMTU (1個のIPパケットで運べる最大の大きさ) を見る |
Permission denied (publickey) | 5 | ここではじめて鍵とユーザー名の話になる |

4段階目で止まる形は実際に踏みました。post-quantum な鍵交換 (量子計算機でも解けないとされる暗号を組み込んだ鍵交換) では、クライアントが公開鍵を載せて送るパケットが 48 バイトから 1232 バイトへ膨らみ、MTU 1280 の経路で TCP が1回に運べる上限 (1228 バイト) を 4 バイト超えます。その経路で接続が止まりました。超えれば必ず止まるとは限らず、どういう条件で止まりうるかはTailscale越しのSSHとMTU 1280の章で、止まった経緯はWSL2 Tailscale SSHがKEXで停止に書きました。
5段階目の括弧を読む
認証まで到達したら、拒否メッセージの括弧の中を見ます。サーバーが何を受け付ける気があるかが書いてあります。
deploy@lab-target: Permission denied (publickey,password).
括弧の中は、サーバーがこの先受け付ける認証方式の一覧です。ここが (publickey) だけなら、パスワードという選択肢自体が提示されていません。入力したパスワードの正誤とは関係がありません。 同じ Permission denied でも原因が違うので、括弧の中身まで読みます。サーバー側でパスワード認証を止めた前後で括弧がどう変わるかは、SSHの鍵認証でパスワードを消す4手順の章で実測しました。
手元で同じログを出す
コンテナ2つで再現できます。2〜4章の実測も、主にこのラボ (4章では踏み台を足して3台) で取りました。5章は実際の tailnet で測り、6章は実行していません。
# sshd を持つイメージを作る
cat > Dockerfile <<'DOCKER'
FROM debian:trixie-slim
RUN apt-get update -qq && apt-get install -y -qq openssh-server rsync \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -s /bin/bash deploy && echo 'deploy:hunter2' | chpasswd \
&& mkdir -p /run/sshd && ssh-keygen -A
CMD ["/usr/sbin/sshd","-D","-e"]
DOCKER
docker build -t sshlab .
docker network create sshlabnet
docker run -d --name lab-target --network sshlabnet sshlab
クライアント側もコンテナにすると、手元の ~/.ssh を汚さずに試せます。
docker run -d --name lab-client --network sshlabnet debian:trixie-slim sleep infinity
docker exec lab-client sh -c 'apt-get update -qq && apt-get install -y -qq openssh-client sshpass rsync'
docker exec lab-client ssh -vv -o StrictHostKeyChecking=no deploy@lab-target true

この章の結論
ssh -vv の最終行を5段階のどれかに置く。それだけで、自分の鍵を見るべきか経路を見るべきかが決まります。次の章では5段階目に進んで、パスワードを鍵に置き換えます。
この記事は役に立ちましたか?