SSH 操作マニュアル

21 分で読了
このページの目次

接続できないときは、ネットワーク、サーバーの身元確認、ユーザー認証のどこで止まったかを調べる。転送では、待ち受ける場所と接続先へアクセスするマシンを先に決める。この二点が分かれば、必要なコマンドを選びやすくなる。

対象は OpenSSH クライアントと Linux サーバー。ops はリモートユーザー、server.example.com はホスト名の例である。以下の server、jump、internal は ~/.ssh/config で定義する別名。実行場所をコメントに記したコマンドは、該当するマシンで実行する。

やりたいことから探す

やりたいこと参照先
ログインする、ポート・ユーザー・鍵を固定する接続と設定
踏み台経由で内部ホストに接続する踏み台接続
公開鍵ログインを設定する、使っている鍵を確認する三種類の鍵ファイルを区別する
リモート側のサービスを使う、サーバーからローカルのサービスを使うポート転送
アップロード、ダウンロード、ディレクトリ同期ファイル転送
タイムアウト、接続拒否、認証失敗、ホスト鍵の変更失敗した段階から調べる

接続と設定

# ローカル:既定 SSH ポートで接続する
ssh ops@server.example.com

初回は host fingerprint を検証する。ops は接続先のアカウント。非既定設定は次を選ぶ。

# ローカル:ポートと鍵を明示する
ssh -p 2222 -i ~/.ssh/id_ed25519_ops -o IdentitiesOnly=yes ops@server.example.com

-p は接続先ポート、-i は手元の秘密鍵。IdentitiesOnly は試す鍵を制限する。前の接続方法との選択肢。

次の設定で server を定義する。保存後は ssh server で接続でき、以下の単発コマンドも手元から実行する。

# ローカル:サーバーで一つ実行して終了する
ssh server 'uname -a'

引用内は遠隔 shell が解釈する。ホストと終了コードを確認する。255 は通常 SSH エラー、それ以外は遠隔コマンドの値の場合がある。

よく使うホストは ~/.ssh/config に記述する。別名は ssh、scp、rsync から共通で使える。

Host server
    HostName server.example.com
    User ops
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_ops
    IdentitiesOnly yes

Host jump
    HostName bastion.example.com
    User ops
    IdentityFile ~/.ssh/id_ed25519_ops
    IdentitiesOnly yes

Host internal
    HostName 10.0.0.20
    User ops
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_ops
    IdentitiesOnly yes

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

個別ホストを先に、共通の既定値を最後に置く。多くの設定項目は最初に得た値が使われ、後ろのブロックで上書きされるわけではない。IdentityFile のように複数の値を蓄積する項目には別の規則がある。IdentitiesOnly yes は設定した認証用の鍵に試行を限定し、agent 内の全鍵を順に試すことを避ける。OpenSSH の設定規則

ServerAliveInterval はサーバーからデータを受け取っていないときに SSH 層の応答確認を行い、無応答が上限に達すると切断する。相手の応答停止を検出でき、中間装置の接続状態を維持できる場合もあるが、自動再接続やリモートプロセスの復元は行わない。

設定が反映されないときは、まず最終的な値を確認する。

# ローカル:接続せず設定を評価する
ssh -G server | grep -E '^(hostname|user|port|identityfile|identitiesonly|proxyjump) '

-G は Host/Match 評価後の値を表示する。特に ProxyJump・IdentityFile を確認する。

古い IP、ポート、誤ったユーザーが出るなら、別名の綴り、一致した Host ブロック、コマンドライン引数を確認する。この段階でサーバーの authorized_keys を変更しても解決しない。

踏み台接続

# ローカル:設定済みの踏み台と最終対象を使う
ssh internal

internal と jump を定義し、ssh -G internal で確認する。

# ローカル:一回限りの踏み台指定を選ぶ
ssh -J ops@bastion.example.com ops@10.0.0.20

-J が踏み台、最後が最終対象。前の遠隔セッション内で続けて実行するものではない。

経路は「ローカル → 踏み台 → 内部ホスト」。ローカルのクライアントが踏み台と最終接続先それぞれの認証を処理し、踏み台は接続先への TCP 転送を担う。秘密鍵を踏み台にコピーしたり、-J のために agent forwarding を有効にしたりする必要はない。各ホストのポートと鍵は、それぞれの Host ブロックに設定する。OpenSSH の踏み台接続

三種類の鍵ファイルを区別する

公開鍵ログインでは、両方向の身元確認が行われる。図の二本の経路を分けて読むと、「サーバーの身元が変わった」と「ユーザーがログインを許可されていない」を区別できる。

サーバーとユーザーの身元を別々に確認 サーバーの身元 ホスト公開鍵 server 信頼する鍵 known_hosts ユーザーの身元 秘密鍵の署名 client 許可する公開鍵 authorized_keys 秘密鍵はローカルに保持
ファイル置く場所役割
id_ed25519_opsローカル、またはローカルの agent が使用ユーザーの身元を証明する秘密鍵
authorized_keysサーバー上のログイン対象ユーザーの .ssh/そのアカウントへのログインを許可するユーザー公開鍵
known_hostsローカル信頼するサーバーのホスト鍵の記録

ログイン用の鍵を追加する

# ローカル:この用途の新しい鍵ペアを作る
ssh-keygen -t ed25519 -C "ops-laptop" -f ~/.ssh/id_ed25519_ops

-f は新しい保存名。既存なら上書きせず中止する。-C は識別用説明。秘密鍵は手元に残し .pub が公開鍵。

# ローカル:既存のアクセス権で公開鍵を登録する
ssh-copy-id -i ~/.ssh/id_ed25519_ops.pub -p 2222 ops@server.example.com

既存の認証手段が必要。遠隔 ops の authorized_keys が対象。.pub だけを送り、生成だけで登録済みとは考えない。

# ローカル:別接続で新しい認証を試す
ssh -o PreferredAuthentications=publickey server 'id -un'

ops が返ることを確認し、新認証が成功するまでは既存接続を保持する。

成功するとリモートユーザー名の ops が表示される。ssh-copy-id には既存のログイン手段が必要で、アクセス権を回避する機能はない。鍵のパスフレーズはローカルで秘密鍵を解除するもので、サーバーのアカウントパスワードとは異なる。準拠要件や互換性の制約がある環境では、対応する鍵の種類を使う。

agent でパスフレーズの再入力を減らす

# ローカル:agent 内の fingerprint を列挙する
ssh-add -l

公開鍵全文は別途 ssh-add -L。どちらも読取専用。

# ローカル:この鍵を現在の agent に 1 時間登録する
ssh-add -t 1h ~/.ssh/id_ed25519_ops

入力するのは秘密鍵の passphrase。ssh-add -l で追加を確認する。SSH_AUTH_SOCK の agent が対象。

# ローカル:不要になったらこの鍵だけを外す
ssh-add -d ~/.ssh/id_ed25519_ops

後で行う解除。-D は全鍵の削除で、この一つだけではない。

authentication agent に接続できない場合は、現在のセッションに有効な SSH_AUTH_SOCK があるかを確認する。デスクトップセッションは既に agent を提供していることが多い。実際に存在しないときだけ現在の shell で eval "$(ssh-agent -s)" を実行し、端末ごとに重複起動しない。

ポート転送:入口と接続先を先に確認する

-L と -R は待ち受けポートの場所を表す。どちらも SSH 接続を通じて双方向にデータを転送する。図の矢印は新しいアプリ接続が入る向きであり、応答が一方向にしか流れないという意味ではない。

図を準備しています
どこで待ち受け、どちらが接続先にアクセスする?

-L はローカルで待ち受け、サーバーから接続先へアクセスする。-R はサーバーで待ち受け、ローカルから接続する。-D はローカルに SOCKS の入口を作り、サーバーがアプリの指定先へ接続する。矢印はアプリ接続の入口の向きで、データは双方向に流れる。

ローカル転送:リモート側のサービスにアクセスする

# ローカル端末 1:ローカル転送を維持する
ssh -N -T -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:8080:127.0.0.1:8000 server

端末を占有する。待ち受け失敗は手元のポートを確認し、動作中に別端末から試す。

# ローカル端末 2:入口から HTTP 対象を試す
curl -v http://127.0.0.1:8080/

状態と本文を確認し、終了後は端末 1 の Ctrl-C で転送を閉じる。

二つの 127.0.0.1 は異なるマシンを指す。最初はローカルの待ち受けアドレス⁠。二つ目はSSH サーバーが接続に使うため、サーバー自身のループバックを指す。接続先がサーバーから到達できる内部ホストなら、後半を db.internal:5432 に変え、データベースクライアントからローカルの 8080 に接続する。接続先の名前解決もサーバー側で行われる。

リモート転送:サーバーからローカルのサービスを使う

# ローカル:自機 8000 用の遠隔入口を作る
ssh -N -T -o ExitOnForwardFailure=yes \
  -R 127.0.0.1:9000:127.0.0.1:8000 server

自機サービスを先に確認し、この端末を維持する。

# サーバー:遠隔側ループバック入口を試す
curl -v http://127.0.0.1:9000/

別のサーバー接続で実行する。転送元クライアントではない。

最初のアドレスはサーバー上での待ち受けを要求し、二つ目にはローカルクライアントが接続する。標準的な用途はサーバー自身からローカルサービスを使うことであり、それだけでインターネットに公開されるわけではない。

サーバーの GatewayPorts がリモートの待ち受け方針を決める。no はループバックに限定、clientspecified はクライアントによる指定を許可、yes はワイルドカードでの待ち受けを強制する。他のマシンからの利用が必要なら、管理者がアドレス、転送権限、入口へのアクセス制御を明示的に決め、実際の待ち受けを確認する。yes を万能な修正として使わない。リモートの待ち受け方針

動的転送:アプリが SOCKS で接続先を指定する

# ローカル端末 1:SOCKS 入口を起動する
ssh -N -T -o ExitOnForwardFailure=yes -D 127.0.0.1:1080 server

動作を維持する。-L/-R と別の用途で、三種類を同時に作る必要はない。

# ローカル端末 2:SOCKS 側で宛先名を解決する
curl --proxy socks5h://127.0.0.1:1080 https://example.com/

h は proxy 側名前解決。応答を確認し、後で端末 1 を Ctrl-C で終了する。

アプリ側の SOCKS 設定が必要。-D を起動するだけではマシン全体の通信はプロキシ化されない。SSH コマンドに固定の接続先はなく、各プロキシリクエストが接続先を指定する。OpenSSH の転送オプション

待ち受け成功と接続先の正常性は別に確認する

-N はリモートコマンドを実行せず、-T は疑似端末を割り当てない。ExitOnForwardFailure=yes は転送の設定に失敗すると終了するが、⁠後から接続先への接続が失敗しても、それだけでは終了しない⁠。転送失敗の扱い

SSH プロセスの存在だけで判断せず、次の三か所を調べる。

調べる場所観察するもの分かること
待ち受けるマシンLinux の ss -ltn で 8080、9000、1080 を探す入口の有無とバインド先。必要なら適切な権限で ss -ltnp を使いプロセスを特定する
接続先へアクセスするマシン対象プロトコルのクライアントで直接接続。本例なら curl -v http://127.0.0.1:8000/正しい側からサービスに到達できるか
アプリの入口転送ポートまたは SOCKS 経由でリクエストする実際のリクエストが全経路を通るか

-L / -D の接続先側はサーバー、-R はローカル。administratively prohibited ならサーバーの転送ポリシーや鍵ごとの制限を確認する。connect failed: Connection refused なら接続先アドレスとサービスの待ち受けを調べる。

ファイル転送:方向とディレクトリ階層を確認する

操作ローカルで実行
ファイルをアップロードscp report.txt server:/tmp/
ファイルをダウンロードscp server:/tmp/report.txt ./
ディレクトリをアップロードscp -r assets server:/tmp/
別名を使わず SCP の SSH ポートを指定scp -P 2222 report.txt ops@server.example.com:/tmp/

ポート指定は scp では大文字の -P、ssh では小文字の -p。ホスト別名を使うと繰り返し指定する手間と間違いを減らせる。

ディレクトリ同期:期待する結果を明記する

ローカルに site/index.html があり、リモートの /srv/www/ は既に存在し、現在のユーザーが書き込めるとする。

転送元コマンド転送後のファイル
site/:中身を転送rsync -av site/ server:/srv/www//srv/www/index.html
site:ディレクトリごと転送rsync -av site server:/srv/www//srv/www/site/index.html

末尾のスラッシュは、転送元ディレクトリの名前で階層を一つ追加するかを変える。rsync のパスの意味

# ローカル:元ディレクトリ内容の反映予定を見る
rsync -av --dry-run --itemize-changes site/ server:/srv/www/

差分と階層を確認する。dry-run は書き込まない。

# ローカル:確認した範囲を同期する
rsync -av --partial --progress site/ server:/srv/www/

終了状態と同じ dry-run で差分を再確認し、サイトも試す。上書きした遠隔ファイルの復元には保存済みの副本が必要。

-a は再帰処理と一般的なメタデータの保存を含む。所有者などを保存できるかは権限による。ACL、拡張属性、ハードリンクの保持は自動では含まれない。--partial は再試行で部分データを利用するためのもので、常に最後のバイトから単純追記するという意味ではない。圧縮が必要なときに -z を加える。これらのコマンドは転送先の余分なファイルを削除しない。意図的なミラーにする場合だけ --delete を検討し、先に削除予定を確認する。

失敗した段階から調べる

ローカルで ssh -vvv server を実行し、最後に成功した段階を探す。以下は代表的なログ断片で、文言はバージョンにより異なる。踏み台を使う場合は、踏み台と最終接続先のログを区別する。

現象・ログ分かっていること次に調べる場所
Could not resolve hostname接続先にはまだ接続していないssh -G の HostName、DNS、別名の綴り
Connection timed out / No route to hostTCP が未接続で、公開鍵の問題とは判断できないアドレス、経路、セキュリティグループ、ファイアウォール、ホストへの到達性
Connection refused接続が能動的に拒否された正しいマシンとポートで待ち受けているか。明示的な拒否規則の可能性もある
Connection establishedTCP は接続済みだがログインは未完了鍵交換、ホストの身元確認、ユーザー認証
REMOTE HOST IDENTIFICATION HAS CHANGED提示されたホスト鍵が記録と異なる独立した経路でサーバーの身元を確認。次節を参照
Offering public key の後に拒否この公開鍵を試したがログインは未許可リモートユーザー、鍵の参照元、権限、認証ポリシー
Too many authentication failures認証試行回数を使い切った設定した鍵。IdentitiesOnly で必要なものに限定する
Authenticated to ...ユーザー認証は成功その後切れるならリモート shell、強制コマンド、セッション制限、サーバーログ

認証失敗のたびに権限を一括変更したり、接続が止まるたびに UseDNS no を設定したりしない。ログから該当する層を特定し、その設定を変更する。

公開鍵が拒否されたら、具体的な鍵を追う

# ローカル:認証に使うユーザー公開鍵の fingerprint を読む
ssh-keygen -lf ~/.ssh/id_ed25519_ops.pub

-l は fingerprint、-f は公開鍵ファイル。対象アカウント側と比較する。

# サーバー対象ユーザー:許可した fingerprint を読む
ssh-keygen -lf ~/.ssh/authorized_keys

手元の公開鍵と比較し、遠隔の対象ユーザーのホームか確認する。

# サーバー対象ユーザー:所有者と mode を確認する
ls -ld ~ ~/.ssh ~/.ssh/authorized_keys

owner/group と他者書込権限を確認し、不適切なパスだけを直す。ホーム全体を再帰 chmod しない。

既定のファイル配置では .ssh は 700、authorized_keys は 600 が一般的で、所有者も正しく設定する。ホームディレクトリを他のユーザーが不適切に書き換えられる状態にしない。異なる AuthorizedKeysFile、集中管理された鍵取得、証明書認証を使うサーバーでは、実際の参照元を確認する。

管理者が調べる場合は、⁠サーバーで実際のサービスユニットを確認する。一般的な名前は ssh.service または sshd.service。

# サーバー管理者:今回の認証拒否理由を読む
sudo journalctl -u ssh.service -u sshd.service --since "10 min ago" --no-pager

元アドレス・ユーザー・時刻で照合する。二つの unit 名は配布版の違いで、両方必要ではない。

# サーバー管理者:ディスク上の設定を検証する
sudo sshd -t

通常、成功時は出力なし。エラーを直してから実 unit を reload し、旧接続を保持したまま新ログインを試す。

一つ目で拒否理由を探す。systemd 以外では該当する認証ログを参照する。二つ目はサーバー設定とホスト鍵の基本的な有効性を検査し、特定ユーザーのログイン成功までは保証しない。設定変更後も既存セッションを残し、別の接続で確認してから閉じる。

ホスト鍵が変わったら、確認後に記録を更新する

既に信頼しているコンソール、または管理者からの独立した連絡経路で新しい指紋を確認する。例えば、サーバーのコンソールで Ed25519 ホスト公開鍵の指紋を読む。

# 信頼できるコンソール:host fingerprint を読む
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

ユーザー鍵ではなく host 公開鍵。クライアント警告のアルゴリズムと合わせる。

ローカルの警告に出たアルゴリズムと指紋の両方に一致する必要がある。別の種類で鍵交換した場合は、該当するホスト公開鍵を確認する。予定どおりのサーバーで、変更理由も確認できてから、⁠警告に対応するホストとポートの古い記録を削除する。

# ローカル:独立した確認後に対応する旧記録を外す
ssh-keygen -R '[server.example.com]:2222'

警告の host/port に合わせ、known_hosts 全体は削除しない。

# ローカル:再接続して新しい fingerprint を確認する
ssh server

信頼経路と一致した場合だけ受け入れる。-R だけでは新端点の信頼性を証明しない。

既定の 22 番ポートなら通常は ssh-keygen -R server.example.com を使う。HostKeyAlias や独自の known-hosts ファイルがある場合は実際の設定に合わせる。再接続時に新しい指紋を照合する。ssh-keyscan は現在のネットワーク上の相手が提示する鍵を集めるだけで、その信頼性を独立に証明するものではない。ホスト鍵の記録操作

関連資料

全オプションは、インストール済みバージョンの man ssh、man ssh_config、man sshd_config を参照する。プロトコルの階層やチャネルの仕組みは SSH プロトコル で解説する。本篇はよく使う作業を探しやすく保ち、詳細な原理は対応する記事で扱う。