2026年7月8日
2026年7月8日
TLSの証明書チェーンとルート認証局の仕組み(検証の流れとopensslでの確認方法)
TLS の証明書チェーンとは、サーバー証明書から中間証明書をたどり、ブラウザや OS があらかじめ信頼しているルート認証局の証明書までつながる「信頼の連鎖」のことです。この記事では、チェーンがどのように検証されるのか、なぜ中間証明書が必要なのかを説明したうえで、openssl で自分のサーバーのチェーンを確認する方法と、チェーンの不備で起きる典型的なエラーの読み方を紹介します。読み終えると「ブラウザでは問題ないのに curl や一部の端末でだけエラーになる」といった現象の理由が分かるようになります。
証明書チェーンを構成する3種類の証明書
一般的な Web サイトのチェーンは、次の3段階で構成されます。
- サーバー証明書(エンドエンティティ証明書):example.com などのドメイン名が書かれた証明書です。中間認証局が署名しています。
- 中間証明書:ルート認証局が署名した、発行専用の認証局の証明書です。1枚のこともあれば、複数枚になることもあります。
- ルート証明書:自分自身で署名した証明書です。OS やブラウザの「信頼ストア」にあらかじめ入っているものだけが信頼の起点になります。
それぞれの証明書には「発行先(Subject)」と「発行者(Issuer)」が書かれています。サーバー証明書の Issuer が中間証明書の Subject と一致し、中間証明書の Issuer がルート証明書の Subject と一致する、という形でつながっています。
クライアントはどうやってチェーンを検証するのか
ブラウザなどのクライアントは、TLS の接続時にサーバーから受け取った証明書を使い、おおむね次の順で確認します。
- サーバー証明書の署名を、中間証明書の公開鍵で検証する
- 中間証明書の署名を、上位の証明書の公開鍵で検証し、これを信頼ストアにあるルート証明書に届くまで繰り返す
- 各証明書が有効期限内か、用途(サーバー認証用か、認証局として署名してよいか)が正しいかを確認する
- サーバー証明書に書かれたドメイン名(SAN)が、接続先のホスト名と一致するかを確認する
- 失効していないかを、クライアントの方式に応じて確認する
どこか1か所でもつながらない、署名が合わない、期限が切れている、といった問題があれば、接続は「信頼できない」と判断されます。
なぜ中間証明書があるのか
ルート証明書の秘密鍵が漏えいすると、その認証局が発行したすべての証明書が信頼できなくなり、世界中の信頼ストアから削除する必要が出てきます。影響が大きすぎるため、ルートの秘密鍵は通常オフラインで厳重に保管され、日々の発行には中間認証局の鍵が使われます。
中間認証局に問題が起きても、その中間証明書だけを失効させ、新しい中間証明書に切り替えれば済みます。認証局が中間証明書を定期的に入れ替えるのはこのためで、サーバー管理者は「中間証明書は変わることがある」と考えておく必要があります。
ルート認証局と信頼ストア
どのルート証明書を信頼するかは、OS やブラウザの提供元がそれぞれの「ルートプログラム」で決めています。Mozilla、Apple、Microsoft、Google(Chrome)などが独自の基準で審査し、問題のある認証局は信頼ストアから外されることがあります。
ここで重要なのは、信頼ストアの中身は端末によって違うということです。古い OS の端末は、新しいルート証明書を持っていないことがあります。逆に、古いルート証明書が期限切れや削除によって使えなくなることもあります。
その対策として使われるのが「クロス署名」です。新しいルート認証局の証明書に、古くから信頼されている別のルート認証局が署名することで、新しいルートを持たない古い端末でもチェーンをたどれるようにします。Let's Encrypt も開始当初はこの方法で互換性を確保していましたが、2021年に古いルート証明書が期限を迎えた際、一部の古い端末や古い OpenSSL で接続エラーが起きました。チェーンの選び方が互換性に直結する例です。
サーバーが送るべき証明書
サーバーが送るのは「サーバー証明書と中間証明書」です。ルート証明書はクライアントがすでに持っている前提なので、送る必要はありません(送っても通常は無視されます)。
順番にも意味があります。先頭にサーバー証明書を置き、その後に中間証明書を、下位から上位の順に並べます。
Let's Encrypt の certbot を使っている場合、/etc/letsencrypt/live/ドメイン名/ に次のファイルが作られます。
- cert.pem:サーバー証明書のみ
- chain.pem:中間証明書のみ
- fullchain.pem:サーバー証明書と中間証明書を連結したもの
- privkey.pem:秘密鍵
Nginx では ssl_certificate に fullchain.pem を指定します。cert.pem を指定すると中間証明書が送られず、チェーンが途切れます。Apache 2.4 では、比較的新しい版であれば SSLCertificateFile に fullchain.pem を指定できます。古い設定例にある SSLCertificateChainFile は非推奨になっているので、使っているバージョンのドキュメントで確認してください。
openssl でチェーンを確認する
自分のサーバーが送っているチェーンは、次のコマンドで確認できます。
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
-servername は SNI(接続先のホスト名)を送る指定です。1台のサーバーで複数のドメインを運用している場合、これがないと別のドメインの証明書が返ってくることがあります。
出力の「Certificate chain」の部分に、次のような行が並びます。
Certificate chain
0 s:CN = example.com
i:C = US, O = Let's Encrypt, CN = R11
1 s:C = US, O = Let's Encrypt, CN = R11
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
s: が Subject、i: が Issuer です。0番の i: と1番の s: が一致していれば、つながっています。中間証明書の名前(上の例の R11)は認証局が入れ替えるため、実際の値は自分の環境の出力で確認してください(2026年10月現在)。
出力の末尾近くにある Verify return code が検証結果です。
- 0 (ok):チェーンの検証に成功しています。
- 21 (unable to verify the first certificate):中間証明書が送られていない場合に多いエラーです。設定ファイルが fullchain.pem を指しているか確認します。
- 20 (unable to get local issuer certificate):手元の信頼ストアで発行元をたどれない状態です。中間証明書の不足のほか、確認する側の CA バンドルが古い場合にも出ます。
- 10 (certificate has expired):チェーン内のどれかの証明書が期限切れです。サーバー証明書だけでなく、古い中間証明書を手動で連結したままになっていないかも確認します。
手元の証明書ファイルの中身は、次のように確認できます。
# 発行先・発行者・有効期限を表示
openssl x509 -in cert.pem -noout -subject -issuer -dates
# 中間証明書を使ってサーバー証明書を検証
openssl verify -untrusted chain.pem cert.pem
ブラウザでは正常なのに一部でエラーになる理由
中間証明書が送られていなくても、ブラウザでは問題なく表示されることがあります。ブラウザによっては、証明書に書かれた発行元の情報(AIA)から中間証明書を自動で取得したり、よく使われる中間証明書をあらかじめ持っていたりするためです。
一方、curl、PHP、各種 API クライアント、一部のスマートフォンアプリなどは、そうした補完をしないことがあります。その結果、「ブラウザでは見えるが、外部サービスからの Webhook や監視ツールだけが失敗する」という現象が起きます。ブラウザでの表示だけで判断せず、openssl や外部の SSL チェックツールでも確認してください。
WordPress が外部へ HTTPS 通信をするとき(プラグインの更新確認や API の呼び出しなど)は、WordPress に同梱された CA バンドルなどを使って相手の証明書を検証します。証明書エラーが出たときに sslverify を false にして回避する例を見かけますが、通信の改ざんを検知できなくなるため避けてください。相手側のチェーンに問題がないか、サーバーの OpenSSL や WordPress 本体が古くないかを先に確認します。
まとめ
- 証明書チェーンはサーバー証明書 → 中間証明書 → ルート証明書の順につながり、Issuer と Subject の一致と署名で検証される
- ルート証明書は OS やブラウザの信頼ストアに入っているものだけが信頼の起点になり、中身は端末によって違う
- サーバーはサーバー証明書と中間証明書を送る。certbot なら fullchain.pem を指定する
openssl s_client -showcertsの s: と i: の対応と Verify return code でチェーンを確認する- ブラウザで正常でも、curl や API クライアントでは中間証明書の不足でエラーになることがある
- WordPress の外部通信で sslverify を無効にせず、チェーンや環境の古さを先に確認する