証明書チェーン(Certificate Chain)
概要
証明書チェーン(Certificate Chain) は、サーバー証明書からルート認証局(Root CA)までの信頼の連鎖を構成する一連の証明書です。RFC 5280 の Section 6 で定義された「Certification Path Validation」アルゴリズムに従い、ブラウザーはチェーン内の各証明書の署名を順に検証します。
チェーンが不完全な場合、ブラウザーは NET::ERR_CERT_AUTHORITY_INVALID(Chrome)や SEC_ERROR_UNKNOWN_ISSUER(Firefox)を表示してページをブロックします。サーバー管理者の視点では「ローカルでは openssl で検証できるのに、特定のクライアントからだけエラーになる」というパターンで現れることが多く、中間証明書の設定漏れが典型的な原因です。
仕組み
証明書チェーンはルート CA・中間 CA・サーバー証明書の 3 階層で構成され、ブラウザーは署名を順にたどって信頼を確立します。
チェーンの構造
証明書チェーンは通常 3 階層で構成されます。
[Root CA 証明書]
└── 署名
[中間 CA 証明書(Intermediate CA)]
└── 署名
[サーバー証明書(End-Entity Certificate)]
ルート証明書は OS やブラウザーのトラストストアにあらかじめ組み込まれています。Windows は「信頼されたルート証明機関」ストア、macOS はキーチェーン、Android は /system/etc/security/cacerts/、Firefox は独自のビルトインストア(NSS)を持っています。
中間証明書はルート CA がサーバー証明書を直接発行せずに間に挟む証明書です。ルート CA の秘密鍵はオフラインで厳重に保管し、日常的な発行操作は中間 CA に委ねます。中間 CA が侵害されても、中間証明書を失効させるだけでルートの信頼性は維持できます。
サーバー証明書は特定のドメインに発行されます。Subject Alternative Name(SAN)フィールドに対象ドメインを列挙します。RFC 2818 では CommonName によるドメイン検証を許容していましたが、Chrome は 2017 年以降 SAN のみを参照します。
検証の流れ
ブラウザーは次の手順で証明書チェーンを検証します。
- サーバーから受け取ったサーバー証明書の Issuer フィールドを確認する
- Issuer と一致する Subject を持つ中間証明書をチェーン内またはキャッシュから探す
- 中間証明書の署名がサーバー証明書に正しく付与されているか検証する
- 中間証明書の Issuer を確認し、トラストストア内のルート証明書まで同じ検証を繰り返す
- ルート証明書に到達し、かつすべての署名が有効であれば信頼を確立する
TLS ハンドシェイクでサーバーが返すのはサーバー証明書と中間証明書です。ルート証明書はクライアント側のトラストストアにあるため送信不要です。
AIA fetching と中間証明書の補完
証明書の Authority Information Access(AIA)拡張フィールドには、中間証明書をダウンロードできる URL が記録されています。Chrome(デスクトップ)や Safari はサーバーが中間証明書を送らない場合にこの URL を使って中間証明書を取得します。これを AIA fetching と呼びます。
ただし Firefox と Android は AIA fetching を実装していません(Chrome on Android を含む)。Firefox の開発者は「サーバーが適切に設定されていれば不要。AIA fetching に依存することは設定ミスを隠蔽する」として機能追加を明示的に拒否しています。このためサーバーが中間証明書を返さない設定になっていると、Chrome デスクトップでは問題なく表示されるのに、Firefox や Android Chrome でエラーになるという非対称な動作が起きます。
Let’s Encrypt のチェーン構造
Let’s Encrypt の証明書チェーンは次の構造を持ちます。
ISRG Root X1(ルート証明書)
└── Let's Encrypt R10(中間証明書)
└── *.example.com(サーバー証明書)
Let’s Encrypt は certbot や acme.sh を使って取得した場合、fullchain.pem にサーバー証明書と中間証明書が連結されています。Nginx の ssl_certificate に fullchain.pem を指定すればチェーンが正しく送出されます。cert.pem を誤って指定すると中間証明書が欠落します。
2021 年 9 月 30 日には Let’s Encrypt が以前から使っていたクロス署名ルート DST Root CA X3 が失効し、広範な障害が発生しました。OpenSSL 1.0.2 を使う古い Linux システム(CentOS 7 など)では、失効した証明書がチェーン検証に影響し HTTPS 接続が失敗しました。この問題は中間証明書の構成だけでなく、クライアント側のトラストストアと OpenSSL のバージョンが絡む複合的なトラブルでした。
証明書チェーンの深さ
CA/Browser Forum のベースライン要件では中間 CA の深さに制限はありませんが、実用上は 2 階層(ルート → 中間 → サーバー)が標準です。一部の CA は、より細分化された運用のために 3 階層(ルート → 中間 CA 1 → 中間 CA 2 → サーバー)の構成を取ります。チェーンが長くなるほど TLS ハンドシェイク時の転送データが増え、初回接続の遅延が大きくなります。
設定例
サーバー証明書と中間証明書を結合し、Nginx に設定する手順を示します。
証明書の手動結合
Nginx で証明書チェーンを正しく設定する例です。
# サーバー証明書と中間証明書を手動で結合する場合
cat server.crt intermediate.crt > fullchain.crt
Nginx での設定
# Nginx の設定
server {
listen 443 ssl;
ssl_certificate /etc/ssl/fullchain.crt;
ssl_certificate_key /etc/ssl/server.key;
}
確認方法
openssl でチェーンを確認するには次のコマンドを使います。
# 接続先のチェーン全体を表示(-showcerts でサーバー証明書と中間証明書を出力)
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# チェーンの検証(ローカルのルート証明書ストアを使う)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 | grep -E "Verify return code|Certificate chain"
検証結果の出力例です。
Certificate chain
Verify return code: 0 (ok)
Verify return code: 0 (ok) であればチェーンが正しく検証されています。21 (unable to verify the first certificate) が返る場合は中間証明書の欠落が疑われます。
openssl で depth=1 の中間証明書が表示されない場合、サーバーが中間証明書を送っていません。Firefox や Android からのアクセスでエラーが出る場合も同じ設定漏れが原因である可能性が高いです。
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から見た結果を取得できます。
curl "https://labee.dev/api/ssl-cert?hostname=example.com"
{
"success": true,
"data": {
"hostname": "example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
data.reachable が true であれば、外部の環境から HTTPS 接続が成立しています。手元の openssl で問題なく見えても外部から reachable: false が返る場合は、中間証明書の欠落やファイアウォールの問題が疑われます。
よくある問題
証明書チェーンの設定ミスは特定のブラウザーやデバイスでのみエラーになるパターンが多く、発見が遅れがちです。
中間証明書の設定漏れ
最も多いトラブルです。ssl_certificate に fullchain.pem ではなくサーバー証明書単体(cert.pem)を指定すると、Firefox と Android ユーザーに SSL エラーが発生します。Chrome デスクトップは AIA fetching で補完するため問題なく表示されます。症状が特定のブラウザー・デバイスに限定される場合はこの問題を疑ってください。
証明書チェーンの順序
チェーンファイルはサーバー証明書を先頭に、中間証明書をその後に記述します。逆順(中間証明書が先)にすると nginx の起動時にエラーになるか、一部のクライアントで検証に失敗します。openssl s_client -showcerts で出力されるチェーンの順序を確認するのが確実です。
期限切れの中間証明書
中間証明書にも有効期限があります。CA が中間証明書を更新した場合、サーバー側のチェーンファイルも更新が必要です。Let’s Encrypt の場合、certbot が fullchain.pem を自動更新するため通常は問題になりません。手動でチェーンを管理している環境では、CA から送られた更新通知を見落とすと期限切れのままになります。
古い Android との互換性
Android 7.1 以前は ISRG Root X1 をトラストストアに持っていません。Let’s Encrypt は 2024 年まで DST Root CA X3 からのクロス署名による互換チェーンを提供していましたが、現在はこのクロス署名チェーンの提供が終了しています。Android 7.1 以前のサポートが必要な場合は、別の CA が発行する証明書か、DST Root CA X3 を信頼するカスタムトラストストアを検討する必要があります。
OCSP ステープリング設定エラー
Nginx で ssl_stapling on; を設定する場合、ssl_trusted_certificate に中間証明書を含むチェーンファイルを指定しないと ssl_stapling_init_cert: can't retrieve issuer certificate エラーが発生します。2025 年 5 月以降、Let’s Encrypt は新規発行証明書への OCSP URL の埋め込みを終了しているため、Let’s Encrypt 証明書では ssl_stapling を無効にするか設定を見直す必要があります。