OpenSSL トラブルシューティング:unable to get local issuer certificate
エラーの解決
OpenSSLを用いて安全な接続を行う際(たとえばPHP環境でのネットワーク通信、openssl
や curl
コマンドの実行、あるいはNode.js/PythonアプリでのSSL/TLS接続時など)、開発者が 20:unable to get local issuer certificate
というエラーメッセージに遭遇することがあります。これは、OpenSSLによる証明書の検証方法に関係した、よくある歴史的な問題です。
セキュリティ上の理由から、OpenSSLは証明書チェーンを検証する際、信頼できるルート証明書機関(CA)を明示的に認識している必要があります。これらの信頼の基点が見つからない、あるいは指定されていない場合、OpenSSLはサーバー証明書の正当性を確認できず、このエラーを報告します。
本記事では、このエラーの原因を詳しく解説し、ServBay環境における対処法を説明します。PHP・Python・Node.js・curl
コマンドなど、各種ツール/言語でOpenSSLの信頼ストアを正しく設定する方法を網羅します。
エラーメッセージ 20:unable to get local issuer certificate
問題の説明
OpenSSLがリモートサーバーのSSL/TLS証明書を検証する際、サーバー証明書から信頼できるルートCAまでの証明書チェーンを構築します。この過程で、チェーンに必要な中間証明書や最終的なルートCA証明書がローカルで見つからない場合、あるいは設定された信頼ストア(CAFileやCAPath)がない場合、検証は失敗し、20:unable to get local issuer certificate
エラーが返されます。
要するに、OpenSSLはどの証明書機関(CA)を信頼してよいかわからず、接続先サーバーの正当性を確認できない状態です。
ServBayにおけるOpenSSLのバージョンとCA証明書パス
ServBayはローカルWeb開発環境として、OpenSSLパッケージと主要な公開CAルート証明書をあらかじめ同梱しています。ServBayで利用されているOpenSSLのバージョンはデバイスのチップ種別によって異なります:
- Apple Silicon (Mシリーズチップ) 用ServBay:OpenSSL 3.2.1バージョン
- Intelチップ用ServBay:OpenSSL 1.1.1uバージョン
これらのOpenSSLバージョンと対応するCA証明書ファイル(cacert.pem
)と証明書ディレクトリ(certs
)は、ServBayのパッケージインストールパス配下に配置されています。ご利用環境に合わせて以下のパスを確認してください:
# すべての信頼済みルート証明書をバンドルしたファイル
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# 単体証明書ファイル格納ディレクトリ(通常cacert.pemで十分ですが、capathが必要な場合も)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
# すべての信頼済みルート証明書をバンドルしたファイル
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# 単体証明書ファイル格納ディレクトリ
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
unable to get local issuer certificate
エラーを解消するには、OpenSSLに上記cafile
またはcapath
のパスを明示的に指定し、信頼できるCA証明書の格納先を教えるのがポイントです。
解決例
ここでは、主要なツールや言語ごとにOpenSSL CA信頼ストアを指定する具体例を紹介します。
例1: openssl
コマンドによる接続テスト
s_client
コマンドで接続テストを行う場合、以下のようなエラーが出ることがあります。
openssl s_client -quiet -connect gmail.com:443
このとき、出力例として verify error:num=20:unable to get local issuer certificate
というエラーが見られます。
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... その他の接続情報 ...
2
3
4
5
6
7
8
解決法:
-CAfile
オプションでServBay提供のCA証明書バンドルファイルのパスを明示します。
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
正常に接続・証明書検証ができれば、出力内容に verify return:1
が表示され、verify error:num=20
は出なくなります。
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... その他の接続情報 ...
2
3
4
5
6
7
例2: PHPでのOpenSSL利用時
PHPの多くのネットワーク機能(例:file_get_contents
でのHTTPS接続, stream_socket_client
のSSL接続, cURL拡張 など)は内部でOpenSSLを利用しています。これらにCA信頼ストアを指定する方法は主に2つあります。
方法A: php.ini
を編集(推奨)
もっともグローバルかつ簡単な方法です。ご利用中のPHPバージョンの php.ini
(ServBayコントロールパネルから編集可)の [openssl]
セクションで、以下を設定します。チップ種別に応じてパスを調整してください。
[openssl]
; 信頼されたCA証明書バンドルファイルを指定
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; CA証明書ディレクトリを指定 (任意ですが推奨)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
5
[openssl]
; 信頼されたCA証明書バンドルファイルを指定
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; CA証明書ディレクトリを指定 (任意ですが推奨)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
5
php.ini
編集後は、ServBayのPHPサービス(またはServBay全体)を再起動してください。
方法B: コード内で設定(接続単位)
グローバル設定を変更したくない場合は、stream_context_create
でSSL/TLSのCA証明書を明示的に指定できます。
<?php
// 例:SSL/TLS対応SMTPサーバーへ接続
$server = 'ssl0.ovh.net';
$port = 465;
// ServBayバージョンに応じてCAファイルパスを設定
// Apple Siliconの場合:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Intelの場合:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // ピア証明書の検証を有効化
'verify_peer_name' => true, // 証明書のホスト名と一致するか検証
'allow_self_signed' => false, // 自己署名証明書は許可しない
'cafile' => $caCertFile, // CA証明書バンドルファイルを指定
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // 任意でCA証明書ディレクトリ指定
],
];
$context = stream_context_create($contextOptions);
// 作成したコンテキストでSSL/TLS接続を実施
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // 接続タイムアウト
STREAM_CLIENT_CONNECT,
$context // コンテキストオプションを渡す
);
if ($connection) {
echo "Connection established to $server:$port\n";
// EHLOコマンド例送信
fwrite($connection, "EHLO servbay.demo\r\n"); // ServBayブランド例のドメイン
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Failed to connect to $server:$port. Error: $errstr ($errno)\n";
}
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
例3: PythonでOpenSSLを利用(sslモジュール)
Pythonのssl
モジュールでも、SSL/TLSコンテキスト作成時に信頼できるCA証明書を指定できます。
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# ServBayバージョンに応じてCAファイルパスを指定
# Apple Siliconの場合:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Intelの場合:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# デフォルトSSLコンテキスト+CAファイル指定
context = ssl.create_default_context(cafile=ca_cert_file)
# capathも指定可能: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# ソケット接続
with socket.create_connection((server, port)) as sock:
# 通常のソケットをSSLソケットにラップ
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# EHLOコマンド例送信
ssock.sendall(b"EHLO servbay.demo\r\n") # ServBayブランド例のドメイン
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Failed to connect or SSL error: {e}")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
例4: Node.jsでのOpenSSL利用(tlsモジュール)
Node.jsのtls
モジュールは、TLS/SSL接続を行うためのものです。信頼するCA証明書をca
オプションとして指定します(ファイル内容を読み込んで渡します)。
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // 代表的な信頼できるサイト例
const port = 443;
// ServBayバージョンに応じてCAファイルパスを指定
// Apple Siliconの場合:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Intelの場合:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// CA証明書内容を読み込む
ca: fs.readFileSync(caCertFile),
// checkServerIdentityはデフォルトで有効(ホスト名検証)です。
// CAが正しく指定され証明書が有効なら、検証は成功するはずです。
// ※特殊な理由・リスクを理解している場合以外は checkServerIdentity をオフにしないこと。
// checkServerIdentity: () => { return null; } // <-- 非推奨!(重大なセキュリティチェックを無効化)
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// HTTPSの場合はHTTPリクエストを送信する必要がありますが、ここでは接続に絞った例です
// socket.write('GET / HTTP/1.1\r\nHost: ' + server + '\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
});
socket.on('close', () => {
console.log('Connection closed');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // エラー内容出力
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
注意: Node.jsの例で checkServerIdentity: () => { return null; }
の記述は省かれています。これはサーバーのホスト名検証をオフにしてしまい、とても危険です。OpenSSLのunable to get local issuer certificate
はCAの信頼性が問題であり、ホスト名検証(verify_peer_name)とは別問題です。正しくca
を指定し、証明書が有効なら、Node.js標準のホスト名検証で正常につながります。万一ホスト名検証エラーが出る場合は証明書自体の問題が考えられます。
例5: curl
コマンドでのOpenSSL指定
curl
コマンドも、HTTPSアクセス時にOpenSSL(または他のSSL実装)を利用します。--cacert
オプションでCA証明書バンドルを指定可能です。
# ServBay提供のCA証明書ファイルを使ってHTTPSサイトを取得
# Apple Siliconの場合:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Intelの場合:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
2
3
4
5
6
CA証明書のパスが正しく、かつサーバー証明書が有効なら、証明書検証エラーは起きません。
まとめ
20:unable to get local issuer certificate
エラーはOpenSSL利用時に頻出しますが、その本質は「信頼ストアが明示されていないため証明書検証ができない」点にあります。ServBayは、代表的なCAルート証明書をバンドルしたcacert.pem
を提供しています。
解決法は、開発環境(php.ini
、コード上のSSLコンテキスト設定、openssl
の-CAfile
やcurl
の--cacert
など)で必ずServBayのcacert.pem
のパスを明示することです。必ずご使用のmacOSチップ種別(Apple Silicon/Intel)と対応するOpenSSLバージョンに合ったパスを指定してください。正しいCA信頼ストア設定により、ローカル開発環境と外部SSL/TLSサービス間の安全な通信が可能になります。