
SSH config の書き方入門 - ~/.ssh/config でホスト別名・踏み台・複数の鍵を整理する
当サイトは 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 |
ひととおり入れた設定ファイルの例は次のとおりです。
# 個別のホスト(具体的なものを上に書く)
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は次の順で設定を集めます。
- コマンドラインのオプション(
-p 2222や-o Port=2222など) - ユーザーごとの設定ファイル(
~/.ssh/config) - システム全体の設定ファイル(
/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アドレスです。
Host web1
HostName 192.0.2.10
User deploy
Port 2222これで、次の2つは同じ意味になります。
ssh -p 2222 deploy@192.0.2.10
ssh web1HostNameを書かなければ、コマンドラインで打った名前がそのまま接続先になります。つまり、DNSで引ける名前のサーバーに対して、ユーザー名や鍵だけを決めておきたいときは、HostNameは要りません。
Host db.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_dbHostには、空白で区切って複数の名前を並べられます。
Host web1 web2 web3
User deployUser・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は試す鍵の一覧に追加されていき、この動作はほかのキーワードと異なる、と書かれています。
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_commonweb1では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です。
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文字 |
! | 先頭に付けると否定 |
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 deployweb?は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 *
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 22HostNameは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 60ssh -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と一緒に追加されました。
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 bastionProxyJumpについて、マニュアルで確認できる注意点は次の3つです。
- 踏み台を複数経由するときは、
ProxyJump bastion1,bastion2のようにカンマで区切ります。書いた順に経由します。 - 接続先のホストに対する設定は、踏み台には適用されません。踏み台のユーザー名や鍵は、上の例のように踏み台の
Hostのまとまりに書きます。 ProxyJump noneと書くと、踏み台の経由を無効にできます。ワイルドカードで広く指定したうえで、特定のホストだけ直接つなぎたいときに使えます。その場合も、noneを書いたまとまりを上に置きます。
ProxyCommandとの違い
ProxyJumpが入る前は、ProxyCommandで同じことをしていました。古い記事でよく見かける書き方です。
Host app1
HostName 198.51.100.21
User deploy
ProxyCommand ssh -W %h:%p bastionProxyCommandは、サーバーへの接続に使うコマンドを指定するキーワードです。sshはそのコマンドの標準入力と標準出力を通してサーバーと通信します。%hは接続先のホスト名、%pはポート番号に置き換えられます。ssh -Wは、標準入出力を指定したホストとポートへ中継するオプションで、OpenSSH 5.4で追加されました。
2つの使い分けは次のとおりです。
| ProxyJump | ProxyCommand | |
|---|---|---|
| 指定するもの | 踏み台のホスト | 接続に使う任意のコマンド |
| 導入 | OpenSSH 7.3 | それ以前からある |
| 向いている用途 | SSHの踏み台を経由する | SSH以外の手段で経路を作る |
SSHの踏み台を経由するだけなら、ProxyJumpのほうが短く、意図も読み取りやすいです。ProxyCommandは、クラウドの接続用コマンドやHTTPプロキシを通すツールなど、SSH以外の方法で経路を作るときに今も使われます。
マニュアルには、この2つは競合し、先に指定されたほうが有効になって、後から出てきたもう一方は効かなくなる、と書かれています。両方を混ぜて書いている設定ファイルでは、どちらが使われているかをssh -Gで確かめてください。
切断を防ぐ: ServerAliveInterval
しばらく操作しないでいると、SSHの接続が固まったり切れたりすることがあります。途中にあるルーターやファイアウォールが、通信のない接続の記録を消してしまうのが主な原因です。
ServerAliveIntervalを設定すると、指定した秒数のあいだサーバーから何も届かなかったときに、暗号化された通信路を通して応答を求めるメッセージを送ります。定期的に通信が発生するので、無通信と見なされにくくなります。
Host *
ServerAliveInterval 60
ServerAliveCountMax 3| キーワード | 意味 | 既定値 |
|---|---|---|
ServerAliveInterval | 応答を求めるメッセージを送るまでの秒数 | 0(送らない) |
ServerAliveCountMax | 応答がないまま送れる回数。達すると切断する | 3 |
TCPKeepAlive | TCPのキープアライブを送るかどうか | yes |
上の設定では、60秒ごとに確認を送り、3回続けて応答がなければ接続を切ります。サーバーが応答しなくなってから切断まで、およそ180秒です。この設定には、切断を防ぐ効果のほかに、相手が落ちたことに気づいて固まった端末を終わらせる効果もあります。
TCPKeepAliveとの違いについて、マニュアルは、ServerAliveのメッセージは暗号化された通信路を通るので偽装できないが、TCPのキープアライブは偽装できる、と説明しています。
サーバー側から同じことをする設定が、sshd_configのClientAliveIntervalです。サーバーの設定を変えられない場合でも、クライアント側のServerAliveIntervalだけで対処できます。
なお、これで防げるのは無通信による切断です。ノートPCをスリープさせたり、ネットワークを切り替えたりした場合の切断は防げません。そうした場面で作業を続けたいなら、サーバー側でtmuxを使い、セッションを残しておく方法が向いています。
接続を多重化する: ControlMaster
仕組みと設定
SSHは、接続のたびに鍵交換と認証を行います。同じサーバーに何度も接続する作業、たとえばgitの操作やrsyncの繰り返し、踏み台を経由する接続では、この待ち時間が積み重なります。
接続の多重化は、最初の接続(マスター)を残しておき、2回目以降はその接続を通してセッションを開く仕組みです。鍵交換と認証を省けるので、すぐにつながります。
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を設定すると、鍵をファイルから読み込んだときに、自動でエージェントへ登録されます。
Host *
AddKeysToAgent yesAddKeysToAgentはOpenSSH 7.2(2016年2月)で追加されたオプションで、既定値はnoです。yesのほかに、登録の前に確認を求めるask、鍵を使うたびに確認を求めるconfirm、エージェントに置いておく時間の指定(1hなど)が使えます。
macOSのUseKeychain
macOSに付属するsshには、パスフレーズをキーチェーンに保存するUseKeychainという独自のオプションがあります。
Host *
AddKeysToAgent yes
UseKeychain yesUseKeychain 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でも案内されている方法です。
Host *
IgnoreUnknown UseKeychain
AddKeysToAgent yes
UseKeychain yesIgnoreUnknownは、それより前に出てきたオプションには効きません。対象のオプションより上に書きます。
鍵を手動でキーチェーンに登録するコマンドは、GitHub Docsによるとssh-add --apple-use-keychain ~/.ssh/id_ed25519です。macOS 12より前では-Kという書き方でした。
設定を分割する: Include
接続先が増えたら、Includeでファイルを分けられます。OpenSSH 7.3(2016年8月)で追加されました。
Include conf.d/*.conf
Host *
ServerAliveInterval 60
AddKeysToAgent yesHost work1
HostName 198.51.100.20
User deployマニュアルで確認できる決まりは次のとおりです。
- ワイルドカードが使え、当てはまったファイルは名前の順に読まれます。
- 絶対パスでないものは、ユーザーの設定ファイルから読み込む場合、
~/.sshからの相対パスとして扱われます。 IncludeはHostやMatchのまとまりの中にも書けて、その場合は条件に当てはまるときだけ読み込まれます。
3つ目の決まりは、つまずきの原因になります。Includeをファイルの末尾に書くと、直前のHostのまとまりの中に入ってしまいます。
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をファイルの先頭に移すと、期待どおりになります。
$ ssh -G work1 | grep -iE '^(hostname|user) '
user deploy
hostname 198.51.100.20Includeは、ファイルの先頭に書くのが安全です。先頭に書けば、読み込んだファイルの中身がHost *より先に評価されるので、評価順の面でも都合がよくなります。
条件で切り替える: Match
Hostはコマンドラインで打った名前だけを条件にしますが、Matchはもっと細かい条件を書けます。OpenSSH 6.5で追加されました。
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 | 手元のネットワークインターフェースのアドレスが、指定した範囲に入っている |
tagged | Tagか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で指定するローカルポートフォワードも、設定ファイルに書いておけます。踏み台の先にあるデータベースへ、手元のポートから接続する例です。
Host db-tunnel
HostName db.internal.example.com
User deploy
ProxyJump bastion
LocalForward 15432 localhost:5432
ExitOnForwardFailure yes
SessionType nonessh 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でも、同じ考え方の設定が紹介されています。
# 個人用
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します。
# 個人用(通常どおり)
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-workURLを毎回書き換えるのが面倒なら、gitのinsteadOfで、特定の組織のURLだけを自動で別名に置き換えられます。これもGitHub Docsで紹介されている方法です。
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に載っている設定は次のとおりです。
Host github.com
Hostname ssh.github.com
Port 443
User gitHostと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.gitweb1という別名も、ユーザー名、ポート、鍵、踏み台の経由、多重化も、そのまま使われます。scpは-P、sshは-pというように、コマンドごとにポートのオプションが違うことを覚える必要もなくなります。
それぞれのコマンドが設定を読む理由は次のとおりです。
| コマンド | 設定が効く理由 |
|---|---|
| scp、sftp | 内部でsshを起動して通信する。-Fと-oはそのままsshに渡される |
| rsync | リモートシェルとしてsshを起動する。-eか環境変数RSYNC_RSHで変えない限りsshが使われる |
| git | SSHの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 ホスト名で、どの設定ファイルが読まれ、どの鍵が試されたかを追うのが近道です。
参考リンク
- ssh_config(5) - OpenBSD manual pages
- ssh(1) - OpenBSD manual pages
- sshd_config(5) - OpenBSD manual pages
- scp(1) - OpenBSD manual pages
- OpenSSH Release Notes
- OpenSSH 7.3 リリースノート(ProxyJump、Include)
- OpenSSH 7.2 リリースノート(AddKeysToAgent)
- OpenSSH 6.8 リリースノート(ssh -G)
- OpenSSH 6.5 リリースノート(Match)
- OpenSSH 5.6 リリースノート(ControlPersist)
- OpenSSH 5.4 リリースノート(ssh -W)
- OpenSSH 9.4 リリースノート(Tag、Match localnetwork)
- openssh/openssh-portable - readconf.c(GitHub)
- GitHub Docs - Managing multiple accounts
- GitHub Docs - Generating a new SSH key and adding it to the ssh-agent
- GitHub Docs - Using SSH over the HTTPS port
- GitHub Docs - GitHub's SSH key fingerprints
- Apple Technical Note TN2449 - OpenSSH updates in macOS 10.12.2
- Git - git-config Documentation
- RFC 5737 - IPv4 Address Blocks Reserved for Documentation


