22
sshd が待ち受けるポート。その裏には `scp`、`rsync`、SSH 越しの `git`、トンネル、そして SFTP がぶら下がっていて、ここが 1 つ拒否されるだけで別々の 5 つが同時に壊れます。
自分の環境の行をそのまま実行してください。ポートを掴んでいるプロセス名と、バインドしているアドレスが出ます。たいていはそれだけで答えが分かります。
sudo lsof -nP -iTCP:22 -sTCP:LISTEN待ち受けソケット 1 つにつき 1 行、COMMAND・PID・USER、そして NAME にバインドアドレスです。`*:22` なら全インターフェイス、`127.0.0.1:22` ならループバックのみ——トンネルの終端としては正当な構成ですし、リモートのクライアントがタイムアウトしかしない理由としてはこれで説明が完結します。`-n` はアドレスのホスト名への解決を、`-P` は `22` の代わりに `ssh` と出すのを止めます。`-sTCP:LISTEN` は確立済みのセッションを隠すので、今つながっている相手も見たいなら外してください。何も出ず lsof が終了コード 1 で終われば誰も持っていません。macOS ではシステム設定でリモートログインを有効にするまで、それが通常の状態です。`sudo` なしでは自分のプロセスしか見えないので、root のデーモンは存在しないように見えます。
sudo ss -tlnp 'sport = :22'`-t` で TCP、`-l` で待ち受けソケットだけ、`-n` でポートを数値のまま、`-p` で所有プロセスを足します。`sport = :22` は送信元ポートに対する ss のフィルタ式です。まず Local Address:Port を読んでください。`0.0.0.0:22` は全 IPv4 インターフェイス、`127.0.0.1:22` はループバックのみ、`[::]:22` は IPv6 のワイルドカードで、多くの Linux では IPv4 も受けます。所有者は `users:(("sshd",pid=812,fd=3))` として出ますが、SSH が socket activation されているホストでは `users:(("systemd",pid=1,fd=53))` になります。これは想定内であって不審ではありません。`ss` が入っていないなら net-tools の `sudo netstat -tlnp | grep ':22 '` で、`-p` が pid/program を斜線区切りで出します。末尾の空白を消すと `:2222` にも当たります。どちらも自分の所有でないプロセスを見るには特権が要り、なければその列が空になるだけです。
netstat -ano | findstr ":22"`-a` で接続と待ち受けポート、`-n` でアドレスとポートを数値のまま、`-o` で最後の列に所有 PID が付きます。欲しい行は State が LISTENING のもので、それ以外はクライアント側のセッションです。PID は `tasklist /FI "PID eq 812"` で名前に変換でき、このフィルタは eq・ne・gt・lt・ge・le を受け付けます。Windows のリスナーは通常、オプション機能の OpenSSH Server が入れる `sshd.exe` で、これは既定では入っていません。一致には気をつけてください。`findstr` は部分文字列で比べるので、`":22"` は `:2222` にも `:22000` にも、22 を含むエフェメラルポートにも当たります。動く前に Local Address の列を最後まで読んでください。
Get-NetTCPConnection -LocalPort 22 | Select-Object LocalAddress,LocalPort,State,OwningProcessポートを部分文字列ではなく数値で一致させるので、`findstr` のように `:2222` を拾うことがありません。`State` が `Listen` で `LocalAddress` が `0.0.0.0` なら全インターフェイス、`127.0.0.1` ならそのマシン自身だけです。`OwningProcess` を `Get-Process -Id` に渡せば実行ファイル名が分かります。結果が空なのに bind が失敗し続けるなら、占有ではなく予約を疑ってください。`netsh interface ipv4 show excludedportrange protocol=tcp` が Hyper-V・WSL2・Docker Desktop が起動時に確保する帯を並べます。その範囲に入ったポートは誰にもバインドできず、責任を負わせる PID も存在しません。
`nmap -Pn -p 22 host.example.com` は、マシンの外から見たそのポートの姿を報告します。`-Pn` は対象をオンライン扱いするので、ping を落とすホストでもスキャンできます。nmap の定義は厳密です。`open` はそこでアプリケーションが実際に接続を受け付けているということ。`closed` は到達できて応答も返ったが待ち受けているものがないということで、これは sshd が止まっているときの姿です。`filtered` はパケットフィルタがプローブをポートまで届かせなかったということで、nmap には open か closed かの区別が付きません——そしてそれこそが、あなたのクライアントを即座に失敗させずに固まらせていたファイアウォールです。`--reason` は実際に返ってきたもの、open なら `syn-ack`、closed なら `conn-refused` を出します。`-sV` はバージョンバナーを読みますが、SSH の場合それは暗号化が始まる前にプロトコルが平文で送る識別文字列、たとえば `SSH-2.0-OpenSSH_9.6` です。この文字列は全世界スキャナーが集めているものそのものなので、中身は公開情報だと思って扱ってください。スキャンは自分が責任を持つホストにだけ。
| 出ているもの | 意味 |
|---|---|
ssh: connect to host host.example.com port 22: Connection refused | ホストは生きていて応答しました——22 番を持つプロセスがないので、カーネルがリセットを返しています。sshd が止まっているか起動に失敗したか、あなたが叩いたアドレスを含まない場所にバインドしているか、設定変更後に別のポートで待ち受けているかです。この失敗は即座に返ってきます。その速さ自体が有益な手がかりで、ネットワークの経路は通っていて足りないのはサービスだけだと分かります。コンソールか別の経路でそのマシンに入り、自分の環境のコマンドを実行してください。 |
ssh: connect to host host.example.com port 22: Connection timed out | 診断としては真逆で、だからこそ 2 つを区別する価値があります。何一つ返ってきていません。ファイアウォール、クラウドのセキュリティグループ、あるいはネットワーク ACL が SYN を黙って捨てているか、そのマシンのものではなくなったアドレスを叩いています。macOS は同じことを `Operation timed out` と言います。数十秒待たされたことが目印です。拒否なら即座に返り、破棄なら接続タイムアウトを最後まで座って待つことになります。`nmap --reason` で確認してください。`filtered` が破棄です。 |
Permission denied (publickey). | TCP もポートもデーモンも正常です——認証まで到達して失敗しただけで、しかもこのメッセージはサーバーが試す気のあった唯一の方式を名指ししています。順番に潰してください。まず `ssh -v` を実行して `Offering public key` の行を読み、クライアントが実際にどの鍵を出したか見ます。鍵をたくさん抱えたエージェントは、正しい鍵に辿り着く前にサーバー側の試行回数の上限を使い切ることがあるので、`-i` と `IdentitiesOnly=yes` で 1 本に固定します。次にサーバー側で、その鍵が当該アカウントの `~/.ssh/authorized_keys` に入っているかを確認します。OpenSSH の `StrictModes` の既定値は yes なので、ホームディレクトリ、`.ssh`、`authorized_keys` のいずれかがグループや他人から書き込める状態だと、sshd はクライアントに何も告げずにそのファイルを無視します。`~/.ssh` は 700、`authorized_keys` は 600、所有者はそのユーザー。本当の理由はサーバーのログが 1 行で書いてくれます。クライアント側には一生出てきません。 |
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! ... Host key verification failed. | サーバーが提示した鍵が、`known_hosts` にその名前で記録されている鍵と違います。そしてこれは、反射的に出てくる対処が間違っている数少ないエラーです。`ssh-keygen -R` から始めないでください。そのホストが以前どうだったかを示す唯一の記録を消してしまいます。無害な原因はいくつもあります。マシンを作り直した、クラウドのアドレスが解放されて誰か別の人のインスタンスに割り当てられた、名前の解決先が別のバックエンドに変わった、転送されたポート越しに別のホストへ届いている。そして無害でない原因が一つ、あなたとサーバーの間に誰かが座っている場合です。まずどちらかを確定させてください。ssh が表示したフィンガープリントを、コンソールで `ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key` を実行して得たものや、インスタンスのブートログに出ているものと突き合わせます。`ssh-keygen -F host` は保存されているエントリとその在り処を教えてくれます。新しいフィンガープリントが確認できて初めて、`ssh-keygen -R host` が正しいコマンドになります。 |
error: Bind to port 22 on 0.0.0.0 failed: Address already in use. | 既に何かがソケットを持っているので sshd が起動できません。たいていは古い sshd がまだ動いている場合で、停止と競走した restart か、パッケージのサービスと張り合っている手元ビルドのどちらかです。socket ユニットが 22 番を持っているところに service ユニットも起動している、というパターンもあります。当てずっぽうではなく上のコマンドで持ち主を特定してください。そして、そのセッション自身が依存している sshd を、コンソールか 2 つ目の入口を確保しないまま止めないこと。 |
ssh_exchange_identification: Connection closed by remote host | TCP の接続は成功していて、sshd がプロトコルのバナーを出す前に切っています。つまりポートも経路もファイアウォールもすべて正常です。よくある原因は、fail2ban か同種のものによる送信元アドレスの ban、負荷やファイルディスクリプタ枯渇で `MaxStartups` が接続を間引いている状態、そして `AllowUsers`・`DenyUsers`・TCP wrappers のようなアクセス規則です。これはネットワークの障害ではなく、サーバーがあなたについて下した判断で、どれなのかはサーバーのログに書いてあります。 |
IANA は 22 番を `ssh`、Secure Shell プロトコルに、TCP・UDP・SCTP で割り当てています。実際に使われるのは TCP だけです。このポートの 1 つのリスナーが運んでいるのは対話的なシェルだけではありません。`scp`、SSH の上で走る `rsync`、`git clone git@host:repo`、`ssh -L` や `-D` のトンネル、`ProxyJump` の踏み台経由、そして SFTP のサブシステム——SFTP と呼ばれているものの正体はこれで、21 番の FTP とは縁もゆかりもありません。OpenSSH が文書化している既定値は `sshd_config` の `Port 22` で、どのインターフェイスにバインドするかは `ListenAddress` が決めます。どちらも複数書けるので、1 台のサーバーが 22 番と別のポートの両方で正しく応答することもあります。プロセスを探しに行く前に一つ。いくつかのディストリビューションは SSH の systemd socket ユニットを同梱するようになりました。`ssh.socket` が有効なホストでは、22 番を持っているのは `systemd` で、`sshd` は接続が来て初めて存在します。これは正常で、他の何かがポートを奪った証拠ではありません。SSH を 22 番から動かすのも 1 行では済みません。`sshd_config` の `Port` を変え、ホストのファイアウォールで新しいポートを開け、SELinux のシステムならさらに `semanage port -a -t ssh_port_t -p tcp 2222` で登録します。この最後の一手を飛ばすと、sshd は起動してバインド失敗をログに書き、他のどこから見ても健康そのものに見えるマシンから締め出されます。新しいポートの確認は、必ず今つながっているセッションを開いたまま、もう 1 つのセッションから行ってください。
22 番は、外に出すことを弁護できる数少ない管理用ポートです。通信は暗号化されていますし、クライアントは秘密を送る前にサーバーのホスト鍵を検証します。とはいえ、それは安全と同義ではありません。22 番のリスナーは数時間で見つかり、そこから `root`・`admin`・`ubuntu`・`git` をはじめ数百の名前に対する、退屈で切れ目のない総当たりを浴び続けます。結果を変える対策は次の 3 つです。1 つ目、鍵だけにして `PasswordAuthentication no`——このオプションの OpenSSH 自身の既定値は `yes` で、ディストリビューションが両方向に上書きしているので、思い込まずに動いている設定を読んでください。2 つ目、`PermitRootLogin` を緩くても文書化された既定値の `prohibit-password`、できれば `no` に。3 つ目、fail2ban のようなレート制限で、数回失敗した送信元アドレスを弾く——これで主に手に入るのは、読めるログです。デーモンを高い番号へ移すとノイズは劇的に減りますが、リスクはほとんど減りません。狙って掛けられたスキャンなら数秒で見つけます。効くのは、ファイアウォールで送信元アドレスを絞ること、`ProxyJump` で辿る踏み台を 1 台だけ立てて 22 番に答えるホストをそこに集約すること、あるいは先に VPN を必須にすることです。どれを選ぶにせよ、確認は既にログインしている 2 つ目のセッションから。締め出しはいつも、繋ぎ直すことでしか確認しなかった変更から始まります。
どれもブラウザ内で完結します。アップロードは発生しません。
22 を調べているとき、ついでに確認することになりがちなポートです。
ほぼ権限の問題です。lsof や ss はソケット自体は出しますが、他ユーザーのプロセスは所有者の列を伏せるので、システムアカウントやコンテナランタイム、launchd が起動したサーバーは sudo を付け直すまで持ち主不明のリスナーに見えます。Windows は逆で、netstat には何も出ないのに bind だけ失敗します。これは Hyper-V や WSL2、Docker Desktop が起動時に予約したポート帯に当たっている状態です。
何なのかを先に見てください。取り残された開発サーバーなら問題ありませんが、書き込み中のデータベースは別ですし、他が依存しているシステムサービスも同様です。持ち主がコンテナならコンテナごと停止してください。ホストから見えるプロセスを kill してもランタイムが再起動するだけです。どうしても止められないものなら、自分のサービスを別のポートへ移すほうが早く片付きます。
0.0.0.0 ではなく 127.0.0.1 にバインドしているからです。各ページのコマンドがプロセス名と並べてバインド先アドレスを出しているのはこのためで、ループバックにバインドしたものはファイアウォールの設定に関係なく自分のマシンからしか届きません。最近は意図的にそれを既定にしているフレームワークも多いので、たいていはバグではなくフラグの問題です。すでに 0.0.0.0 なのに届かないなら、次に疑うのはホストのファイアウォールかクラウドのセキュリティグループです。
ほとんどなりません。インターネット全体を舐めるスキャナーはポート範囲を丸ごと走査し続けているので、稼げるのは数時間であって安全ではなく、代わりに関係者全員がその番号を覚える羽目になります。既定ポートを狙う無差別スキャンのログが減るという効果は確かにありますが、それだけです。結果を変えるのは認証と、送信元アドレスを絞ったファイアウォールと、そもそも外に出さないことです。
ポート 22 でまだ詰まっていますか。ポート一覧をすべて見る。あるいは上に戻って、実際に出たエラー文から追ってください。