2026年5月24日

2026年5月24日

SSL証明書の中間証明書が抜けている場合の確認方法と直し方

「パソコンのブラウザでは問題なく開けるのに、スマートフォンの一部や curl、WordPress の外部連携でだけ証明書エラーになる」という場合、サーバーが中間証明書を送っていないことが最も多い原因です。この記事では、中間証明書の抜けを openssl で見分ける方法、よくある原因を起こりやすい順に挙げたうえでの確認と修正の手順、直した後の検証方法を説明します。修正作業そのものは、多くの場合「証明書ファイルの指定を1行変えて再読み込みする」だけで終わります。

なぜ「一部の環境だけ」でエラーになるのか

サーバー証明書は中間認証局が署名しており、クライアントは「サーバー証明書 → 中間証明書 → ルート証明書」とたどって信頼を確認します。ルート証明書は OS やブラウザに最初から入っていますが、中間証明書はサーバーがサーバー証明書と一緒に送るのが原則です。

ところが、デスクトップのブラウザの一部は、サーバーが中間証明書を送らなくても、過去に見た中間証明書のキャッシュや、証明書内に書かれた取得先(AIA)から自動で補ったり、主要な中間証明書をあらかじめ持っていたりします。そのため、自分のブラウザで確認しただけでは問題に気づけません。一方、curl、PHP、Python、Java などのプログラムや、一部のモバイル端末はこうした補完をしないことが多く、そこでだけエラーになります。

症状とエラーメッセージの例

次のような表示が出ていれば、中間証明書の抜けを疑います。

  • curl:SSL certificate problem: unable to get local issuer certificate
  • WordPress / PHP:cURL error 60: SSL certificate problem: unable to get local issuer certificate(外部サービスから自サイトの Webhook や REST API が呼べない、という形で表面化することもあります)
  • Python:CERTIFICATE_VERIFY_FAILED と unable to get local issuer certificate
  • openssl s_client:Verify return code: 21 (unable to verify the first certificate)
  • SSL Labs などの診断サイト:チェーンに関する警告(Chain issues: Incomplete など)

同じエラー文は「クライアント側の信頼ストアが古い」場合にも出るため、次の手順でサーバー側の問題かどうかを切り分けます。

openssl でチェーンを確認する

echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null \
  | grep -E "^ *[0-9]+ s:|^ *i:|Verify return code"

出力の 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
Verify return code: 0 (ok)

s: が証明書の持ち主(Subject)、i: が発行者(Issuer)です。正常な状態では、0番の発行者と1番の持ち主が一致し、最後の発行者が信頼ストアにあるルート証明書になります。中間証明書が抜けていると、0 の1行しかなく、Verify return code: 21 が表示されます。なお、上の中間証明書の名前は一例で、認証局は中間証明書を定期的に切り替えるため、実際の表示は異なります(2026年10月現在)。

-servername を付け忘れると、SNI が送られず別のサイトの証明書を確認してしまうことがあります。1台に複数のドメインがあるサーバーでは特に注意してください。

原因と直し方(起こりやすい順)

1. Let’s Encrypt で cert.pem を指定している

Certbot が作るファイルのうち、cert.pem はサーバー証明書だけ、fullchain.pem はサーバー証明書と中間証明書を連結したものです。Web サーバーに cert.pem を指定していると、中間証明書が送られません。

# 現在の指定を確認
sudo grep -rn "ssl_certificate\b\|SSLCertificateFile" /etc/nginx /etc/apache2 /etc/httpd 2>/dev/null

Nginx なら ssl_certificate を、Apache なら SSLCertificateFile を fullchain.pem に変えます。秘密鍵の privkey.pem はそのままで構いません。

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

2. 購入した証明書で中間証明書を設定していない

認証局から証明書を受け取ると、サーバー証明書とは別に「中間証明書」「CA バンドル」などの名前でファイルが届くのが一般的です。サーバー証明書だけを設定すると中間証明書が抜けます。Nginx では、サーバー証明書を先頭に、中間証明書をその後ろに連結した1つのファイルを作って指定します。

# 作業前に既存ファイルをバックアップ
sudo cp -a /etc/ssl/example.com /etc/ssl/example.com.bak.$(date +%F)

cat server.crt intermediate.crt | sudo tee /etc/ssl/example.com/fullchain.crt > /dev/null

ルート証明書は連結しなくて構いません。クライアント側に入っているものが使われるため、送っても通信量が増えるだけです。連結するファイルの末尾に改行が無いと、-----END CERTIFICATE----------BEGIN CERTIFICATE----- のように行がつながり、読み込みエラーになることがあります。連結後のファイルをエディタで開いて確認してください。

3. Apache の設定方法がバージョンと合っていない

Apache 2.4.8 以降では、中間証明書を含めた連結ファイルを SSLCertificateFile に指定するのが標準です。古い手順書にある SSLCertificateChainFile は非推奨になっています。古い設定から移行するときに、SSLCertificateChainFile の行だけを消して SSLCertificateFile を連結ファイルに変え忘れると、中間証明書が抜けます。

SSLCertificateFile    /etc/ssl/example.com/fullchain.crt
SSLCertificateKeyFile /etc/ssl/example.com/server.key

4. 中間証明書の種類や順番が違う

認証局は中間証明書を切り替えることがあり、古い手順書に載っている中間証明書や、別の種類の証明書用の中間証明書を連結しているケースがあります。サーバー証明書の発行者と、中間証明書の持ち主が一致しているかを確かめます。

openssl x509 -in server.crt -noout -issuer
openssl x509 -in intermediate.crt -noout -subject

# 手元でチェーンを検証(ルートはシステムの信頼ストアを使う)
openssl verify -untrusted intermediate.crt server.crt

server.crt: OK と表示されれば組み合わせは正しく、エラーが出る場合は認証局のサイトから、その証明書に対応する中間証明書を取り直します。

Nginx で連結の順番を逆(中間証明書が先)にすると、設定テストの段階で key values mismatch のエラーになります。先頭の証明書と秘密鍵が対応しているかは次のように確認できます。2つの出力が同じであれば対応しています。

openssl x509 -in server.crt -noout -pubkey | openssl sha256
openssl pkey -in server.key -pubout | openssl sha256

5. 修正したのに古い証明書が返り続ける

ファイルを直しても、Web サーバーを再読み込みしていなければ反映されません。また、ロードバランサー、CDN、別のバーチャルホストなど、実際に証明書を返しているのが別の場所というケースもあります。クラウドのロードバランサーに証明書をインポートした場合は、インポート時に「証明書チェーン」にあたる欄へ中間証明書を入れたかを確認してください。レンタルサーバーの管理画面でも、中間証明書(CA 証明書)の入力欄が別に用意されていることが多く、欄の名称はサービスによって異なります。

修正後の確認

sudo nginx -t && sudo systemctl reload nginx
# Apache の場合
sudo apachectl configtest && sudo systemctl reload apache2   # RHEL系は httpd

# 再確認
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep "Verify return code"
curl -sI https://example.com/ | head -1

Verify return code: 0 (ok) が表示され、curl がエラーなく応答を返せば修正は完了です。外部の診断サイトでもチェーンの警告が消えたことを確かめておくと、別の経路での問題にも気づけます。

Let's Encrypt の場合、更新時にファイルの中身は自動で入れ替わりますが、指定しているファイル名が cert.pem のままだと、更新のたびに同じ問題が起きます。自動更新の後に Web サーバーが再読み込みされる設定になっているかも合わせて確認してください。

WordPress 側で証明書エラーが出たときの注意

WordPress がほかのサーバーへ接続するときに cURL error 60 が出る場合、問題があるのは接続先のサーバーの中間証明書かもしれません。この場合、自分のサーバーの設定を変えても直りません。上の openssl コマンドを接続先のホスト名に向けて実行し、チェーンが欠けていれば接続先の管理者に連絡します。

エラーを消すために、プラグインやコードで証明書の検証(sslverify)を無効にする方法が紹介されていることがありますが、通信の相手を確認しないまま接続することになり、なりすましに気づけなくなります。一時的な切り分け以外の目的では使わないでください。自サーバー側の信頼ストアが古いことが原因の場合は、OS の ca-certificates パッケージを更新するのが正しい対処です。

まとめ

  • 中間証明書の抜けは、デスクトップのブラウザでは気づきにくく、curl・PHP・一部のモバイル端末でエラーになるのが特徴
  • openssl s_client -showcerts -servername でチェーンの枚数と Verify return code を見れば、サーバー側の問題かを判断できる
  • 最も多い原因は Let's Encrypt の cert.pem 指定と、購入した証明書の中間証明書の設定漏れ。fullchain.pem や連結ファイルに変える
  • 連結はサーバー証明書が先、中間証明書が後。openssl verify -untrusted と公開鍵の比較で組み合わせを確認できる
  • 証明書を返しているのがロードバランサーや CDN の場合はそちらを直す。WordPress で検証を無効にしてエラーを隠すのは避ける

お気軽にご相談ください

お見積りへ お問い合わせへ
▲