SSH config の書き方入門 - ~/.ssh/config でホスト別名・踏み台・複数の鍵を整理する

SSH config の書き方入門 - ~/.ssh/config でホスト別名・踏み台・複数の鍵を整理する

作成日:
読了:約52分
更新日:

当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。

サーバーが増えてくると、ssh -i ~/.ssh/id_ed25519_work -p 2222 deploy@192.0.2.10のような長いコマンドを、シェルの履歴から探して実行するようになります。踏み台を経由する接続や、GitHubのアカウントごとに鍵を変える運用が加わると、毎回オプションを打つのは現実的ではありません。

こうした接続先ごとの違いをまとめて書いておく場所が、OpenSSHクライアントの設定ファイル、通称SSH config(~/.ssh/config)です。書いておけばssh web1だけでつながり、scpやrsync、gitからも同じ設定が使われます。

この記事では、~/.ssh/configの基本的な書き方から、複数の鍵の使い分け、踏み台の経由、切断の防止、接続の多重化、ファイルの分割、よくあるエラーの原因までを、実行結果付きで説明します。SSHが接続を確立して認証する仕組みそのものはSSH の仕組みで扱っているので、この記事は設定ファイルの実用に絞ります。

NOTE

2026年11月14日に、ssh_config(5)とssh(1)のマニュアル、OpenSSHのリリースノート、GitHub Docs、AppleのTechnical Note TN2449で仕様と導入バージョンを確認しました。コマンドの実行結果は、macOS 27.2に付属するOpenSSH 10.5p1のものです。ssh -Gの実行結果は、記事に載せた設定を一時ディレクトリのファイルに書き、ssh -F ファイル名 -G ホスト名で読ませて得ました(記事中のコマンドでは-Fを省いています)。エラーメッセージのうち「表示例」と書いたものは、実際に発生させたものではなく、OpenSSHのソースコードにある文言をもとにした例です。執筆時点でopenssh.comに掲載されている最新版は、OpenSSH 10.6(2026年10月6日公開)と、移植版の10.6p2(2026年11月5日公開)です。サンプルのホスト名にはexample.comを、IPアドレスにはRFC 5737の文書用アドレスを使っています。

先に結論: よく使う設定の早見表

まず、使う頻度が高い設定を並べます。それぞれの意味は後の章で順に説明します。

やりたいこと書く設定
接続先に短い名前を付けるHost web1とHostName 192.0.2.10
ユーザー名とポートを決めておくUser deployとPort 2222
使う鍵を指定するIdentityFile ~/.ssh/id_ed25519_work
指定した鍵だけを使うIdentitiesOnly yes
踏み台を経由するProxyJump bastion
無通信での切断を防ぐServerAliveInterval 60
接続を使い回して速くするControlMaster auto、ControlPath、ControlPersist
鍵をエージェントに自動で登録するAddKeysToAgent yes
ファイルを分割するInclude conf.d/*.conf
ポートフォワードを決めておくLocalForward 15432 localhost:5432
最終的な設定を確かめるssh -G web1

ひととおり入れた設定ファイルの例は次のとおりです。

~/.ssh/config
# 個別のホスト(具体的なものを上に書く)
Host web1
    HostName 192.0.2.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes
 
# 踏み台
Host bastion
    HostName bastion.example.com
    User jump
 
# 踏み台の先にあるサーバー
Host *.internal.example.com
    User deploy
    ProxyJump bastion
 
# 全体の既定値(いちばん下に書く)
Host *
    ServerAliveInterval 60
    AddKeysToAgent yes

つまずきやすい点も4つ挙げておきます。

  • 設定は上から順に読まれ、項目ごとに最初に得られた値が使われます。具体的なホストを上に、Host *を下に書きます。
  • Hostは自分で付ける別名で、HostNameが実際の接続先です。
  • 鍵をたくさん持っているなら、IdentityFileと一緒にIdentitiesOnly yesを書きます。
  • 設定が思ったとおりに効いているかは、ssh -G ホスト名で確かめられます。接続はしません。

SSH config とは

読み込まれる場所と優先順位

ssh_config(5)によると、sshは次の順で設定を集めます。

  1. コマンドラインのオプション(-p 2222や-o Port=2222など)
  2. ユーザーごとの設定ファイル(~/.ssh/config)
  3. システム全体の設定ファイル(/etc/ssh/ssh_config)

そして、項目ごとに最初に指定された値が使われます。コマンドラインがいちばん強く、次が~/.ssh/config、最後が/etc/ssh/ssh_configという順番です。普段編集するのは~/.ssh/configだけで足ります。ファイルが無ければ自分で作ります。

設定ファイルを作る
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/config
chmod 600 ~/.ssh/config

なお、サーバー側の設定ファイルは/etc/ssh/sshd_configで、名前がよく似ています。この記事で扱うのは、接続する側(クライアント)の設定です。

書式

書式は「キーワード 値」を1行に1つずつ並べるだけです。

書式の例
# 行頭が # の行はコメント
Host web1
    HostName 192.0.2.10    # 行末にもコメントを書ける
    User deploy
    Port=2222

マニュアルで確認できる決まりは次のとおりです。

  • キーワードと値は、空白か、=で区切ります。Port 2222でもPort=2222でも同じです。
  • キーワードは大文字と小文字を区別しません。値は区別します。
  • 行頭と行末の空白は無視されます。字下げは見やすさのためのもので、文法上の意味はありません。
  • 空白を含む値は、ダブルクォートで囲みます。
  • HostかMatchの行から、次のHostかMatchの行までが、1つのまとまりになります。

字下げに意味がないという点は大事です。Hostの行より上に書いた設定や、ファイルの途中に字下げなしで書いた設定は、見た目に関係なく、直前のHostのまとまりに入ります。

基本: 接続先に名前を付ける

HostとHostNameの違い

Hostは、コマンドラインで打つ名前に対する条件です。HostNameは、実際に接続する先のホスト名かIPアドレスです。

~/.ssh/config
Host web1
    HostName 192.0.2.10
    User deploy
    Port 2222

これで、次の2つは同じ意味になります。

同じ接続
ssh -p 2222 deploy@192.0.2.10
ssh web1

HostNameを書かなければ、コマンドラインで打った名前がそのまま接続先になります。つまり、DNSで引ける名前のサーバーに対して、ユーザー名や鍵だけを決めておきたいときは、HostNameは要りません。

HostNameを省く例
Host db.example.com
    User admin
    IdentityFile ~/.ssh/id_ed25519_db

Hostには、空白で区切って複数の名前を並べられます。

1つのまとまりに複数の名前
Host web1 web2 web3
    User deploy

User・Port・IdentityFile

よく使う3つのキーワードの意味と既定値は次のとおりです。

キーワード意味書かなかった場合
Userログインするユーザー名手元のユーザー名
Port接続先のポート番号22
IdentityFile認証に使う秘密鍵のファイル~/.ssh/id_ed25519などの既定のファイル

IdentityFileの既定は、手元のOpenSSH 10.5p1では~/.ssh/id_rsa、~/.ssh/id_ecdsa、~/.ssh/id_ecdsa_sk、~/.ssh/id_ed25519、~/.ssh/id_ed25519_sk、~/.ssh/id_mldsa44_ed25519の6つでした。既定のファイルの一覧はバージョンによって変わるので、手元の値は後で紹介するssh -Gで確かめてください。

IdentityFileに書くのは秘密鍵のパスです。.pubの付かないほうを指定します。公開鍵と秘密鍵の関係は公開鍵暗号とデジタル署名 入門で説明しています。

コマンドラインでssh root@web1のようにユーザー名を付けた場合は、コマンドラインのほうが優先されます。設定ファイルのUserは、省略したときの値だと考えると分かりやすいです。

複数の鍵を使い分ける

IdentityFileは追加されていく

ほとんどのキーワードは最初の値だけが使われますが、IdentityFileは例外です。マニュアルに、複数のIdentityFileは試す鍵の一覧に追加されていき、この動作はほかのキーワードと異なる、と書かれています。

~/.ssh/config
Host web1
    HostName 192.0.2.10
    IdentityFile ~/.ssh/id_ed25519_work
 
Host *
    IdentityFile ~/.ssh/id_ed25519_common
実行結果
$ ssh -G web1 | grep -i '^identityfile'
identityfile ~/.ssh/id_ed25519_work
identityfile ~/.ssh/id_ed25519_common

web1では2つの鍵が順に試されます。Host *にIdentityFileを書くと、すべてのホストで試す鍵が1つ増えることになります。

Too many authentication failuresの原因

鍵を使い分けているのに、次のようなエラーで接続を切られることがあります。下は、OpenSSHのソースコード(サーバー側のauth.cとクライアント側のpacket.c)にある文言をもとにした表示例です。

表示例
Received disconnect from 192.0.2.10 port 22:2: Too many authentication failures

これは、サーバーが受け付ける認証の試行回数を超えたときに、サーバー側から送られるメッセージです。sshd_config(5)のMaxAuthTriesが1回の接続で許す認証の試行回数で、既定値は6です。

一方、クライアントはIdentityFileで指定した鍵だけを使うわけではありません。マニュアルのIdentityFileの説明には、IdentitiesOnlyを設定していない限り、認証エージェント(ssh-agent)が持っている鍵も認証に使う、と書かれています。エージェントに鍵が何本も登録されていると、目的の鍵にたどり着く前に、関係のない鍵で試行回数を使い切ってしまいます。

IdentitiesOnly yesで使う鍵を絞る

対処は、IdentitiesOnly yesを書くことです。エージェントがほかの鍵を持っていても、設定された鍵だけを使うようになります。既定値はnoです。

~/.ssh/config
Host web1
    HostName 192.0.2.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

マニュアルには、このオプションはエージェントが多くの鍵を差し出す状況のためのもの、と説明されています。鍵を複数持っているなら、IdentityFileとIdentitiesOnly yesを組にして書く、と覚えておくと安全です。

設定ファイルを直す前に原因を確かめたいときは、コマンドラインで同じ指定をして試せます。

コマンドラインで試す
ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes deploy@192.0.2.10

どの鍵が試されているかは、ssh -v web1の出力で確認できます。

Hostパターンと評価順

ワイルドカードと否定

Hostには、次のパターンが使えます。

記号意味
*0文字以上の任意の文字列
?任意の1文字
!先頭に付けると否定
~/.ssh/config
Host *.example.com !bastion.example.com
    ProxyJump bastion.example.com
 
Host web? db-*
    User deploy
実行結果
$ ssh -G app.example.com | grep -i '^proxyjump'
proxyjump bastion.example.com
 
$ ssh -G bastion.example.com | grep -i '^proxyjump'
(何も出力されない)
 
$ ssh -G web3 | grep -i '^user '
user deploy
 
$ ssh -G db-main | grep -i '^user '
user deploy

web?はweb3には当てはまりますが、web10には当てはまりません。?は1文字だけに対応するためです。

否定のパターンは、当てはまった場合にそのHostの行全体を無効にします。上の例では、bastion.example.comは*.example.comに当てはまりますが、!bastion.example.comにも当てはまるので、このまとまりは使われません。踏み台自身が踏み台を経由しようとして接続できなくなる、という事故を防ぐ書き方です。

パターンが照合される相手は、コマンドラインで打った名前です。HostNameで置き換えた後の名前ではありません。Host web1でHostName 192.0.2.10と書いていても、Host 192.0.2.*のまとまりはssh web1には当てはまりません。

最初に得られた値が使われる

設定ファイルは上から順に読まれ、当てはまるまとまりの設定がすべて集められます。そのとき、同じキーワードが複数回出てきたら、最初の値が使われます。マニュアルには、ホスト固有の宣言はファイルの先頭近くに、一般的な既定値は末尾に書くべきだ、とあります。

逆の順番で書くと、次のようになります。

悪い例: Host * を上に書いた
Host *
    User fallback
    Port 22
 
Host web1
    HostName 192.0.2.10
    User deploy
    Port 2222
実行結果
$ ssh -G web1 | grep -iE '^(hostname|user|port) '
user fallback
hostname 192.0.2.10
port 22

HostNameはHost *に書かれていないのでweb1の値が使われますが、UserとPortは先に出てきたHost *の値で決まってしまい、web1の設定は無視されています。エラーも警告も出ないので、気づきにくいつまずきです。CSSのように後から書いたものが勝つのではなく、先に書いたものが勝ちます。

正しい順番にすると、個別の値が使われ、書かなかった項目だけがHost *から補われます。

良い例: 具体的なものを上に
Host web1
    HostName 192.0.2.10
    User deploy
    Port 2222
 
Host *
    User fallback
    ServerAliveInterval 60
実行結果
$ ssh -G web1 | grep -iE '^(hostname|user|port|serveraliveinterval) '
user deploy
hostname 192.0.2.10
port 2222
serveraliveinterval 60

ssh -Gで最終的な設定を確かめる

ここまでの実行結果で使ってきたssh -Gは、HostとMatchを評価した後の設定を表示して終了するオプションです。OpenSSH 6.8(2015年3月)で追加されました。サーバーには接続しないので、気軽に試せます。

最終的な設定を表示する
# すべての項目を表示する(手元では81行)
ssh -G web1
 
# 見たい項目だけに絞る
ssh -G web1 | grep -iE '^(hostname|user|port|identityfile|proxyjump) '

出力のキーワードはすべて小文字です。設定ファイルに書いていない項目も既定値と一緒に表示されるので、既定値を調べる手段としても使えます。たとえば、設定ファイルを読まずに既定値だけを見るには-F noneを付けます。

既定値を見る
$ ssh -F none -G example.com | grep -iE '^(port|identitiesonly|serveraliveinterval|serveralivecountmax|tcpkeepalive|controlmaster|addkeystoagent) '
port 22
controlmaster false
identitiesonly no
tcpkeepalive yes
serveralivecountmax 3
serveraliveinterval 0
addkeystoagent false

設定を書き換えたら、接続を試す前にssh -Gで値を見る習慣を付けておくと、評価順の間違いにすぐ気づけます。

踏み台を経由する

ProxyJump

外から直接つなげないサーバーに、踏み台(bastion)を経由して接続する設定です。ProxyJumpは、OpenSSH 7.3(2016年8月)で、コマンドラインの-Jと一緒に追加されました。

~/.ssh/config
Host bastion
    HostName bastion.example.com
    User jump
    IdentityFile ~/.ssh/id_ed25519_bastion
    IdentitiesOnly yes
 
Host app1
    HostName 198.51.100.21
    User deploy
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes
    ProxyJump bastion

これでssh app1を実行すると、まずbastionにSSHで接続し、そこからapp1へのTCP転送を張って、その上でapp1とのSSH接続を行います。認証は手元のマシンとapp1のあいだで行われるので、app1用の秘密鍵を踏み台に置く必要はありません。

HostNameに書く198.51.100.21は、踏み台から見た接続先です。手元のマシンから直接届かないアドレスでもかまいません。

設定ファイルに書かず、その場で踏み台を指定することもできます。

コマンドラインで踏み台を指定する
ssh -J jump@bastion.example.com deploy@198.51.100.21

踏み台の先にあるサーバーをまとめて設定するなら、ワイルドカードを使います。

踏み台の先をまとめて設定する
Host *.internal.example.com
    User deploy
    ProxyJump bastion

ProxyJumpについて、マニュアルで確認できる注意点は次の3つです。

  • 踏み台を複数経由するときは、ProxyJump bastion1,bastion2のようにカンマで区切ります。書いた順に経由します。
  • 接続先のホストに対する設定は、踏み台には適用されません。踏み台のユーザー名や鍵は、上の例のように踏み台のHostのまとまりに書きます。
  • ProxyJump noneと書くと、踏み台の経由を無効にできます。ワイルドカードで広く指定したうえで、特定のホストだけ直接つなぎたいときに使えます。その場合も、noneを書いたまとまりを上に置きます。

ProxyCommandとの違い

ProxyJumpが入る前は、ProxyCommandで同じことをしていました。古い記事でよく見かける書き方です。

ProxyCommandで踏み台を経由する(以前の書き方)
Host app1
    HostName 198.51.100.21
    User deploy
    ProxyCommand ssh -W %h:%p bastion

ProxyCommandは、サーバーへの接続に使うコマンドを指定するキーワードです。sshはそのコマンドの標準入力と標準出力を通してサーバーと通信します。%hは接続先のホスト名、%pはポート番号に置き換えられます。ssh -Wは、標準入出力を指定したホストとポートへ中継するオプションで、OpenSSH 5.4で追加されました。

2つの使い分けは次のとおりです。

ProxyJumpProxyCommand
指定するもの踏み台のホスト接続に使う任意のコマンド
導入OpenSSH 7.3それ以前からある
向いている用途SSHの踏み台を経由するSSH以外の手段で経路を作る

SSHの踏み台を経由するだけなら、ProxyJumpのほうが短く、意図も読み取りやすいです。ProxyCommandは、クラウドの接続用コマンドやHTTPプロキシを通すツールなど、SSH以外の方法で経路を作るときに今も使われます。

マニュアルには、この2つは競合し、先に指定されたほうが有効になって、後から出てきたもう一方は効かなくなる、と書かれています。両方を混ぜて書いている設定ファイルでは、どちらが使われているかをssh -Gで確かめてください。

切断を防ぐ: ServerAliveInterval

しばらく操作しないでいると、SSHの接続が固まったり切れたりすることがあります。途中にあるルーターやファイアウォールが、通信のない接続の記録を消してしまうのが主な原因です。

ServerAliveIntervalを設定すると、指定した秒数のあいだサーバーから何も届かなかったときに、暗号化された通信路を通して応答を求めるメッセージを送ります。定期的に通信が発生するので、無通信と見なされにくくなります。

~/.ssh/config
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
キーワード意味既定値
ServerAliveInterval応答を求めるメッセージを送るまでの秒数0(送らない)
ServerAliveCountMax応答がないまま送れる回数。達すると切断する3
TCPKeepAliveTCPのキープアライブを送るかどうかyes

上の設定では、60秒ごとに確認を送り、3回続けて応答がなければ接続を切ります。サーバーが応答しなくなってから切断まで、およそ180秒です。この設定には、切断を防ぐ効果のほかに、相手が落ちたことに気づいて固まった端末を終わらせる効果もあります。

TCPKeepAliveとの違いについて、マニュアルは、ServerAliveのメッセージは暗号化された通信路を通るので偽装できないが、TCPのキープアライブは偽装できる、と説明しています。

サーバー側から同じことをする設定が、sshd_configのClientAliveIntervalです。サーバーの設定を変えられない場合でも、クライアント側のServerAliveIntervalだけで対処できます。

なお、これで防げるのは無通信による切断です。ノートPCをスリープさせたり、ネットワークを切り替えたりした場合の切断は防げません。そうした場面で作業を続けたいなら、サーバー側でtmuxを使い、セッションを残しておく方法が向いています。

接続を多重化する: ControlMaster

仕組みと設定

SSHは、接続のたびに鍵交換と認証を行います。同じサーバーに何度も接続する作業、たとえばgitの操作やrsyncの繰り返し、踏み台を経由する接続では、この待ち時間が積み重なります。

接続の多重化は、最初の接続(マスター)を残しておき、2回目以降はその接続を通してセッションを開く仕組みです。鍵交換と認証を省けるので、すぐにつながります。

~/.ssh/config
Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm-%C
    ControlPersist 10m
キーワード意味既定値
ControlMaster接続の共有を有効にするか。autoは、マスターがあれば使い、なければ自分がマスターになるno
ControlPath共有に使うソケットファイルのパスなし
ControlPersist最初のセッションを閉じた後、マスターを残しておく時間no

ControlPathの%Cは、手元のホスト名・接続先のホスト名・ポート・ユーザー名などから作られるハッシュに置き換えられます。OpenSSH 6.7で追加されたトークンで、ソケットのパスが長くなりすぎるのを避けられます。マニュアルは、ControlPathには少なくとも%h、%p、%rを含めるか、代わりに%Cを使い、ほかのユーザーが書き込めないディレクトリに置くことを勧めています。

ControlPersistは、OpenSSH 5.6(2010年8月)で追加されました。10mと書くと、接続しているセッションがなくなってから10分間、マスターを裏で残します。yesか0にすると、明示的に終了させるまで残り続けます。

ssh -Gで見ると、トークンが展開された結果を確認できます。

実行結果
$ ssh -G db | grep -iE '^(controlmaster|controlpath|controlpersist) '
controlmaster auto
controlpath ~/.ssh/cm-6028f4d8c85f4b874eca503b7c8e0625fdfa2ca8
controlpersist 600

マスター接続を操作する

残っているマスター接続は、ssh -Oで操作します。

マスター接続の確認と終了
# マスターが動いているか確認する
ssh -O check web1
 
# マスターを終了させる
ssh -O exit web1

注意点

多重化は便利ですが、接続を使い回すことによる注意点があります。

  • マスターが残っているあいだは、新しい認証が行われません。鍵やユーザーの設定を変えて試すときは、先にssh -O exitでマスターを終了させます。
  • マニュアルによると、多重化した接続でもエージェント転送とX11転送は使えますが、転送されるのはマスター接続のものです。
  • マニュアルによると、同じControlPathのソケットに接続したセッションは、新しく接続を張らずにマスターのネットワーク接続を再利用します。マニュアルは、ControlPathをほかのユーザーが書き込めないディレクトリに置くことを勧めています。共用のマシンでは特に、ソケットの置き場所に気を付けてください。

特定のホストだけ多重化を使いたくない場合は、そのホストのまとまりにControlMaster noかControlPath noneを書きます。これもHost *より上に置きます。

鍵のパスフレーズとエージェント

AddKeysToAgent

パスフレーズを付けた鍵は、使うたびにパスフレーズを聞かれます。ssh-agentに鍵を登録しておけば、その後は聞かれません。AddKeysToAgent yesを設定すると、鍵をファイルから読み込んだときに、自動でエージェントへ登録されます。

~/.ssh/config
Host *
    AddKeysToAgent yes

AddKeysToAgentはOpenSSH 7.2(2016年2月)で追加されたオプションで、既定値はnoです。yesのほかに、登録の前に確認を求めるask、鍵を使うたびに確認を求めるconfirm、エージェントに置いておく時間の指定(1hなど)が使えます。

macOSのUseKeychain

macOSに付属するsshには、パスフレーズをキーチェーンに保存するUseKeychainという独自のオプションがあります。

~/.ssh/config(macOS)
Host *
    AddKeysToAgent yes
    UseKeychain yes

UseKeychain yesにすると、鍵を使うときにキーチェーンからパスフレーズを探し、入力したパスフレーズが正しければキーチェーンに保存します。既定値はnoです。再起動した後も、パスフレーズを入力し直さずに鍵を使えるようになります。

この2つを書く必要が出てきた経緯は、AppleのTechnical Note TN2449に書かれています。macOS 10.12.2で、鍵をエージェントに自動で読み込む動作と、パスフレーズをキーチェーンに保存する動作が既定ではなくなり、必要ならAddKeysToAgent yesとUseKeychain yesを設定するように、という内容です。

UseKeychainはApple版だけのオプションなので、同じ設定ファイルをLinuxで使ったり、Homebrewで入れたOpenSSHで読んだりすると、エラーで止まります。下は、手元でつづりを変えたオプションを読ませたときの出力をもとに、パスとオプション名を置き換えた表示例です。

表示例
/Users/deploy/.ssh/config: line 3: Bad configuration option: usekeychain
/Users/deploy/.ssh/config: terminating, 1 bad configuration options

設定ファイルを複数の環境で共有するなら、IgnoreUnknownで知らないオプションを無視させます。TN2449でもGitHub Docsでも案内されている方法です。

~/.ssh/config
Host *
    IgnoreUnknown UseKeychain
    AddKeysToAgent yes
    UseKeychain yes

IgnoreUnknownは、それより前に出てきたオプションには効きません。対象のオプションより上に書きます。

鍵を手動でキーチェーンに登録するコマンドは、GitHub Docsによるとssh-add --apple-use-keychain ~/.ssh/id_ed25519です。macOS 12より前では-Kという書き方でした。

設定を分割する: Include

接続先が増えたら、Includeでファイルを分けられます。OpenSSH 7.3(2016年8月)で追加されました。

~/.ssh/config
Include conf.d/*.conf
 
Host *
    ServerAliveInterval 60
    AddKeysToAgent yes
~/.ssh/conf.d/work.conf
Host work1
    HostName 198.51.100.20
    User deploy

マニュアルで確認できる決まりは次のとおりです。

  • ワイルドカードが使え、当てはまったファイルは名前の順に読まれます。
  • 絶対パスでないものは、ユーザーの設定ファイルから読み込む場合、~/.sshからの相対パスとして扱われます。
  • IncludeはHostやMatchのまとまりの中にも書けて、その場合は条件に当てはまるときだけ読み込まれます。

3つ目の決まりは、つまずきの原因になります。Includeをファイルの末尾に書くと、直前のHostのまとまりの中に入ってしまいます。

悪い例: Includeを末尾に書いた
Host web1
    HostName 192.0.2.10
 
Include conf.d/*.conf
実行結果
$ ssh -G work1 | grep -iE '^(hostname|user) '
user (手元のユーザー名)
hostname work1

この実行結果は、設定を一時ディレクトリに置いて-Fで読ませたものです。相対パスのIncludeは~/.sshから探されるので、確認のときだけIncludeの行を一時ディレクトリの絶対パス(Include /一時ディレクトリ/conf.d/*.conf)に書き換えています。userの行には、実際には手元のユーザー名が表示されます。

IncludeがHost web1のまとまりに入ったため、web1に接続するとき以外は読み込まれず、work1の設定が効いていません。Includeをファイルの先頭に移すと、期待どおりになります。

Includeを先頭に移した後
$ ssh -G work1 | grep -iE '^(hostname|user) '
user deploy
hostname 198.51.100.20

Includeは、ファイルの先頭に書くのが安全です。先頭に書けば、読み込んだファイルの中身がHost *より先に評価されるので、評価順の面でも都合がよくなります。

条件で切り替える: Match

Hostはコマンドラインで打った名前だけを条件にしますが、Matchはもっと細かい条件を書けます。OpenSSH 6.5で追加されました。

~/.ssh/config
Host web1
    HostName web1.example.com
 
# 接続先が example.com で、ユーザーが deploy のときだけ
Match host *.example.com user deploy
    IdentityFile ~/.ssh/id_ed25519_deploy
 
# 残り全部
Match all
    ServerAliveInterval 30
実行結果
$ ssh -G deploy@web1 | grep -iE '^(hostname|identityfile|serveraliveinterval) '
hostname web1.example.com
serveraliveinterval 30
identityfile ~/.ssh/id_ed25519_deploy
 
$ ssh -G root@web1 | grep -i '^identityfile'
identityfile ~/.ssh/id_rsa
identityfile ~/.ssh/id_ecdsa
(以下、既定のファイルが続く)

主な条件は次のとおりです。

条件当てはまるとき
host接続先のホスト名がパターンに合う(HostNameで置き換えた後の名前)
originalhostコマンドラインで打った名前がパターンに合う
user接続先のユーザー名が合う
localuser手元のユーザー名が合う
exec指定したコマンドの終了コードが0
localnetwork手元のネットワークインターフェースのアドレスが、指定した範囲に入っている
taggedTagかssh -Pで付けたタグが合う
all常に当てはまる

Hostとの大きな違いは、Match hostがHostNameで置き換えた後の名前と照合する点です。上の例でも、web1という別名で接続しているのに、*.example.comの条件に当てはまっています。別名を付けたホストをドメインでまとめて扱いたいときに使えます。

条件は、先頭に!を付けると否定になります。たとえば、社内のネットワークにいないときだけ踏み台を経由する、という設定が書けます。localnetworkとTagは、OpenSSH 9.4(2023年8月)で追加されました。

社内ネットワークの外にいるときだけ踏み台を使う
Match host *.internal.example.com !localnetwork 192.0.2.0/24
    ProxyJump bastion.example.com

マニュアルはlocalnetworkについて、ネットワークのアドレスは多くの状況で信頼できる基準ではないので、安全性に関わる設定の切り替えに使うときは注意するように、と書いています。接続先のネットワークがたまたま同じアドレス範囲を使っていることもあるためです。

execは接続のたびにコマンドを実行します。時間のかかるコマンドを書くと、すべての接続が遅くなる点に気を付けてください。

ポートフォワードを設定に書く: LocalForward

ssh -Lで指定するローカルポートフォワードも、設定ファイルに書いておけます。踏み台の先にあるデータベースへ、手元のポートから接続する例です。

~/.ssh/config
Host db-tunnel
    HostName db.internal.example.com
    User deploy
    ProxyJump bastion
    LocalForward 15432 localhost:5432
    ExitOnForwardFailure yes
    SessionType none
トンネルを張る
ssh db-tunnel
# 別の端末から localhost:15432 に接続すると、
# db.internal.example.com の 5432 番ポートに届く
  • LocalForwardの1つ目の値は手元で待ち受けるポート、2つ目は接続先のサーバーから見た転送先です。この例のlocalhostは、手元のマシンではなくdb.internal.example.com自身を指します。
  • ExitOnForwardFailure yesは、ポートがすでに使われているなどで転送を設定できなかったときに、接続を終了させます。既定値はnoで、その場合は転送に失敗してもログインだけが成功します。
  • SessionType noneは、リモートでコマンドを実行せず、転送だけを行う指定です。コマンドラインの-Nと同じ意味で、OpenSSH 8.7で追加されました。

ssh -L、-R、-Dの違いと仕組みは、SSH の仕組みのポートフォワーディングの章で説明しています。

GitHubの複数アカウントを使い分ける

個人用と仕事用のように、GitHubのアカウントを2つ使い分ける場面があります。GitHubへのSSH接続は、どのアカウントでもユーザー名がgitで、ホスト名がgithub.comです。アカウントは鍵で区別されるので、接続するときにどの鍵を差し出すかを切り替える必要があります。

Hostの別名で切り替える

Hostに別名を付け、HostNameを同じgithub.comにする方法です。GitHub Docsでも、同じ考え方の設定が紹介されています。

~/.ssh/config
# 個人用
Host github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_personal
    IdentitiesOnly yes
 
# 仕事用
Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_work
    IdentitiesOnly yes

ここではIdentitiesOnly yesを書いておきます。ssh_config(5)のIdentityFileの説明にあるとおり、IdentitiesOnlyを設定しない限り、エージェントが持っているほかの鍵も認証に使われるためです。GitHub Docsも、この行について、エージェントが複数の鍵を読み込んでいても正しい鍵が使われるようにするものだ、と説明しています。

仕事用のリポジトリは、ホスト名の部分を別名に変えてcloneします。

別名を使ってcloneする
# 個人用(通常どおり)
git clone git@github.com:sample-user/dotfiles.git
 
# 仕事用(github.com を github-work に変える)
git clone git@github-work:sample-org/app.git

どのアカウントとして認証されるかは、次のコマンドで確かめられます。

認証されるアカウントを確かめる
ssh -T git@github.com
ssh -T git@github-work

URLを毎回書き換えるのが面倒なら、gitのinsteadOfで、特定の組織のURLだけを自動で別名に置き換えられます。これもGitHub Docsで紹介されている方法です。

特定の組織のURLを別名に置き換える
git config --global url."git@github-work:sample-org/".insteadOf "git@github.com:sample-org/"

gitの設定で切り替える

SSHの設定ではなく、gitの側で使う鍵を指定する方法もあります。gitのcore.sshCommandは、fetchやpushで使うsshコマンドを差し替える設定です。

リポジトリごとに鍵を指定する
git config core.sshCommand "ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes"

この方法なら、リモートのURLはgit@github.com:...のままで済みます。gitの設定やaliasについてはよく使う Git コマンド一覧も参考にしてください。

22番ポートが使えない場合

社内のネットワークなどで22番ポートへの接続が遮断されているときは、GitHubが用意している443番ポートでのSSH接続を使えます。GitHub Docsに載っている設定は次のとおりです。

~/.ssh/config
Host github.com
    Hostname ssh.github.com
    Port 443
    User git

HostとHostNameを分けて書けることが、ここでも役に立っています。リモートのURLはgithub.comのままで、実際の接続先だけが変わります。切り替えた後の最初の接続では、ホスト鍵の確認を求められることがあります。表示されたフィンガープリントがGitHubが公開しているものと一致するか確かめてから進めてください。

パーミッション: Bad owner or permissions

設定ファイルを別のマシンからコピーした後などに、次のエラーで接続できなくなることがあります。下は、OpenSSHのソースコード(readconf.c)にある文言をもとにした表示例で、パスは環境によって変わります。

表示例
Bad owner or permissions on /home/deploy/.ssh/config

マニュアルには、~/.ssh/configは悪用されるおそれがあるので厳格なパーミッションが必要で、本人が読み書きでき、ほかの人が書き込めないようにする、とあります。設定ファイルを他人が書き換えられると、ProxyCommandなどを通じて、本人の権限で任意のコマンドを実行させられるためです。

実際の検査は、OpenSSHのソースコード(readconf.c)で確認できます。次のどちらかに当てはまるとエラーになります。

  • ファイルの所有者が、rootでも自分でもない
  • グループか、その他のユーザーに書き込み権限が付いている

つまり、644は通りますが、664や666は通りません。ほかの人に読ませる理由もないので、600にしておくのが無難です。

所有者とパーミッションを直す
ls -l ~/.ssh/config
chown "$USER" ~/.ssh/config
chmod 600 ~/.ssh/config
chmod 700 ~/.ssh

よくあるのは、アーカイブを展開したときや、共有フォルダ経由でコピーしたときに、グループの書き込み権限が付いてしまう場合と、別のユーザーのファイルをそのままコピーして所有者が違っている場合です。

この検査が行われるのは、既定の場所にあるユーザーの設定ファイルです。ソースコードと手元の実行結果では、-Fで指定したファイルは検査されていませんでした。Includeで読み込んだファイルにも同じ検査が行われるかは、今回は確認できていません(未確認)。分割したファイルも600にそろえておけば問題は起きません。

パーミッションの数字の読み方や、chmodとchownの使い方は、Linux パーミッションの仕組みで説明しています。

scp・rsync・gitでも同じ設定が効く

~/.ssh/configの設定は、sshコマンドだけのものではありません。内部でsshを使うコマンドは、同じ設定を読みます。

別名はほかのコマンドでも使える
# scp
scp ./backup.tar.gz web1:/tmp/
 
# sftp
sftp web1
 
# rsync(リモートシェルの既定がssh)
rsync -av ./public/ web1:/var/www/html/
 
# git
git clone web1:/srv/git/app.git

web1という別名も、ユーザー名、ポート、鍵、踏み台の経由、多重化も、そのまま使われます。scpは-P、sshは-pというように、コマンドごとにポートのオプションが違うことを覚える必要もなくなります。

それぞれのコマンドが設定を読む理由は次のとおりです。

コマンド設定が効く理由
scp、sftp内部でsshを起動して通信する。-Fと-oはそのままsshに渡される
rsyncリモートシェルとしてsshを起動する。-eか環境変数RSYNC_RSHで変えない限りsshが使われる
gitSSHのURLに対してsshを起動する。core.sshCommandか環境変数GIT_SSH_COMMANDで変えられる

多重化を設定していると、gitのfetchやrsyncを続けて実行したときに、2回目以降の接続が速くなります。

sshコマンドを呼び出して通信するツールであれば、同じように設定が効きます。たとえばlftpのSFTP接続は内部でsshを起動するので、~/.ssh/configに書いたポートや鍵、踏み台がそのまま使われます。

一方、sshコマンドを呼び出さず、SSHのプロトコルを自前のライブラリで扱うクライアントは、~/.ssh/configを読むとは限りません。GUIのクライアントやエディターの拡張機能、プログラミング言語のSSHライブラリがどこまで設定を読むかは、それぞれの文書で確かめてください。サーバーの疎通確認は、HTTPならcurl、SSHならssh -vと、道具を分けて考えると原因を切り分けやすくなります。

よくあるつまずき

Host * を上に書いて個別の設定が効かない

評価順の章で説明したとおり、最初に得られた値が使われます。Host *はファイルのいちばん下に置きます。ssh -G ホスト名で値を見れば、どの設定が使われたかが分かります。

HostNameを書き忘れて名前解決に失敗する

Host web1にHostNameを書き忘れると、web1という名前をそのままDNSで引こうとして、Could not resolve hostnameで失敗します。別名を付けるときはHostNameが必要です。

字下げでまとまりを区切れると思っている

字下げは無視されます。まとまりを終わらせるのは、次のHostかMatchの行だけです。全体に効かせたい設定をファイルの途中や末尾に字下げなしで書いても、直前のHostのまとまりに入ります。全体に効かせる設定はHost *の中に書きます。

キーワードのつづりを間違えている

知らないキーワードがあると、sshはBad configuration optionを出して終了します。どのホストに接続するときでも止まるので、設定ファイルの1か所の間違いで、すべての接続ができなくなります。エラーメッセージに行番号が出るので、その行を直します。

多重化で古い接続が使われ続ける

ControlPersistでマスターが残っていると、設定を変えても古い接続が使われます。鍵やユーザー、踏み台の設定を変えたのに動作が変わらないときは、ssh -O exit ホスト名でマスターを終了させてから試します。

踏み台の設定を接続先のまとまりに書いている

ProxyJumpを使うとき、接続先のホストに書いたUserやIdentityFileは踏み台には適用されません。踏み台への接続でPermission deniedになるときは、踏み台用のHostのまとまりを作り、そこにユーザー名と鍵を書きます。

まとめ

  • ~/.ssh/configは接続する側の設定ファイル。コマンドライン、~/.ssh/config、/etc/ssh/ssh_configの順に読まれ、項目ごとに最初の値が使われる
  • Hostは自分で付ける別名(パターン)、HostNameは実際の接続先
  • 具体的なホストを上に、Host *を下に書く。逆にすると個別の設定が無視される
  • IdentityFileだけは値が追加されていく。鍵が多いときはIdentitiesOnly yesを組にして、Too many authentication failuresを防ぐ
  • 踏み台はProxyJump(OpenSSH 7.3以降)。踏み台自身の設定は踏み台のHostに書く。SSH以外の経路を作るときはProxyCommand
  • 無通信での切断はServerAliveIntervalで防ぐ。既定値は0で、何も送らない
  • ControlMaster auto、ControlPath、ControlPersistで接続を使い回せる。設定を変えたらssh -O exit
  • macOSではAddKeysToAgent yesとUseKeychain yes。ほかの環境と共有するならIgnoreUnknown UseKeychain
  • Includeはファイルの先頭に書く。末尾に書くと直前のHostのまとまりに入る
  • GitHubの複数アカウントは、Hostの別名とIdentitiesOnly yesで鍵を切り替える
  • Bad owner or permissionsは、所有者が違うか、グループやその他に書き込み権限があるときに出る。chmod 600で直す
  • 設定はscp、sftp、rsync、gitでも使われる

設定を書き換えたら、まずssh -G ホスト名で最終的な値を確かめてください。接続せずに結果が分かるので、評価順の間違いや、パターンの当てはまり方の勘違いにすぐ気づけます。それでも原因が分からないときは、ssh -v ホスト名で、どの設定ファイルが読まれ、どの鍵が試されたかを追うのが近道です。

参考リンク

lftp で SFTP サーバーに接続する - 鍵認証・ポート指定・mirror での同期まで

lftp で SFTP サーバーに接続する - 鍵認証・ポート指定・mirror での同期まで

約12分

コマンドラインの高機能ファイル転送クライアント lftp で SFTP サーバーに接続する方法を、基本の sftp:// URL、パスワード/公開鍵認証、ポート番号や踏み台の指定、ホスト鍵の確認、mirror での一括ダウンロード/アップロード、リトライやタイムアウトの設定、bash スクリプトでの自動化まで実例つきで解説します。lftp の SFTP は内部で ssh を呼ぶ(sftp:connect-program)ため、ssh の設定や鍵がそのまま効くのがポイントです。

curl コマンドの使い方 - HTTP リクエストの送り方とデバッグに使うオプション

curl コマンドの使い方 - HTTP リクエストの送り方とデバッグに使うオプション

約70分

curlはURLを指定してデータを送受信するコマンドです。GETと-o・-L・-f・-i・-vの基本、-dと--data-binaryと--jsonでのPOST、-Fのファイル送信、-uやBearerトークン、Cookieの扱い、-wでのステータスコードと時間の計測、--resolveと--connect-to、--retryとタイムアウト、終了コード、設定ファイルと変数展開、並列転送までを、curl 8.23.0の実行結果付きで説明します。-X POSTが要らない理由、-dで改行が消える問題、-LとPOSTのメソッド変化などのつまずきも扱います。

jq の使い方入門 - JSON をコマンドラインで整形・抽出・変換する基本とハマりどころ

jq の使い方入門 - JSON をコマンドラインで整形・抽出・変換する基本とハマりどころ

約59分

jqはJSONを読み込み、フィルタで加工した結果を出力するコマンドです。整形の.、フィールド参照と.[]、パイプとカンマ、select・map・to_entries・group_by、--argと--argjson、-r・-c・-s・-n・-e、@csvや@base64、文字列補間、//と|=とreduceまでを、jq 1.8.1の実行結果付きで説明します。ファイルの上書きで中身が空になる失敗、NDJSONと--stream、終了ステータス、数値の精度、gojqやjaqなどの代替ツールも扱います。

楽天市場のおすすめPR / 楽天アフィリエイト