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)を知っている必要があります。信頼アンカーが見つからない、または指定されていない場合、サーバー証明書の正当性を検証できず、このエラーが発生します。
本記事ではこのエラーの背景を解説し、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が「どの証明機関を信頼すべきか」分からず、接続先サーバーの正当性を判定できない状態です。
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のインストールパス以下にあります。ご利用のプラットフォーム・アーキテクチャに従い、以下のパスを参照してください。
ini
# 信頼できるルート証明書バンドル
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# 個別証明書ファイル格納ディレクトリ(通常cacert.pemだけで十分ですが、capath指定が必要な場合はこちら)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# 信頼できるルート証明書バンドル
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# 個別証明書ファイル格納ディレクトリ
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# 信頼できるルート証明書バンドル
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# 個別証明書ファイル格納ディレクトリ
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
このエラーを解消するためには、OpenSSLを使用する際に必ず上記のcafile
やcapath
を明示指定し、どのCA証明書を参照するか伝えることが重要です。
解決方法の例
各種ツールや言語でOpenSSLトラストストアの指定方法を解説します。
例1: openssl
コマンドによる接続テスト
openssl s_client
コマンドで接続テストをする際、もし次のようなエラーが表示された場合:
bash
openssl s_client -quiet -connect gmail.com:443
1
出力に「verify error:num=20:unable to get local issuer certificate」が含まれることがあります:
bash
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
# ... その他の接続情報 ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
解決策:
-CAfile
パラメータでServBay提供のCA証明書バンドルファイルを指定してください。
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
1
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
1
証明書検証が成功すると、verify return
値が1
となり、verify error:num=20
の行が表示されません。
bash
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
# ... その他の接続情報 ...
1
2
3
4
5
6
7
2
3
4
5
6
7
例2: PHPでのOpenSSL利用
PHPの各種ネットワーク処理(file_get_contents
のHTTPS通信、stream_socket_client
によるSSL接続、cURL拡張など)は内部でOpenSSLを利用しています。CAトラストストアを指定するには、php.ini
の編集、もしくはコード内でstream contextオプションを設定できます。
方法A: php.ini
の編集(推奨)
最も手軽なグローバル設定です。使用中のPHPバージョンに対応するphp.ini
ファイルを編集し(ServBayコントロールパネルから編集可能)、[openssl]
セクションにてopenssl.cafile
とopenssl.capath
を設定します。
ini
[openssl]
; 信頼できるCA証明書バンドル
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; CA証明書を格納したディレクトリ(任意、だが推奨)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[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
1
2
3
4
5
2
3
4
5
php.ini
を編集したら、ServBay内のPHPサービス(またはServBay全体)を再起動し、設定内容を反映させましょう。
方法B: コード内で設定(接続単位で有効)
グローバルなphp.ini
変更が不要な場合は、stream_context_create
を使ったSSL/TLS接続時にcafile
を指定できます。
php
<?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);
// 上記contextでSSL/TLS接続
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if ($connection) {
echo "$server:$portへの接続に成功しました\n";
// EHLOコマンド例送信
fwrite($connection, "EHLO servbay.demo\r\n"); // ServBayブランド名を用いた例示のドメイン
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "$server:$portへの接続失敗。エラー: $errstr ($errno)\n";
}
?>
1
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
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
例3: PythonでのOpenSSL利用(sslモジュール)
Pythonのssl
モジュールでは、SSL/TLSコンテキスト作成時にCA証明書を指定できます。
python
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接続完了。交渉済みプロトコル: {ssock.version()}")
# EHLO送信例
ssock.sendall(b"EHLO servbay.demo\r\n")
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"接続失敗またはSSLエラー: {e}")
1
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
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
モジュールでSSL/TLS接続を行う場合、接続オプションのca
属性でCA証明書を指定します。文字列またはBuffer配列形式でcacert.pem
の内容を渡すのが一般的です。
javascript
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),
// Node.js tlsモジュールはデフォルトでホスト名検証(checkServerIdentity)を実行
// CAファイル指定が正しく、サーバー証明書が有効ならば検証が成功します。
// 特別な理由がなければcheckServerIdentityの無効化は避けるべきです。
// checkServerIdentity: () => { return null; } // <-- この行は推奨されません。重要な安全性検証が無効になります!
};
const socket = tls.connect(options, () => {
console.log('SSL接続に成功しました');
// 通常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('接続が切断されました');
});
socket.on('error', (error) => {
console.error('エラー:', error.message);
});
1
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
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
コマンドもOpenSSL(または他のSSLライブラリ)を用いてHTTPSリクエストを行います。CA証明書バンドルは--cacert
オプションで指定可能です。
bash
# 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
1
2
3
4
5
6
2
3
4
5
6
CA証明書パスが正しく、サーバー側証明書が有効であれば、curl
は証明書検証エラーを出さずにコンテンツを取得します。
まとめ
「20:unable to get local issuer certificate」エラーはOpenSSLでSSL/TLS接続する際によく発生します。根本的な原因は、OpenSSLがサーバー証明書を検証するために信頼できるトラストストア設定を必要とするためです。ServBayは主要なパブリックCAルート証明書をパッケージしたcacert.pem
ファイルを提供しています。
解決方法は、開発環境(php.ini
、コード内SSLコンテキスト指定、openssl
コマンドの-CAfile
やcurl
の--cacert
)でServBayのcacert.pem
ファイルパスを確実に指定することです。macOSのチップ種別(Apple SiliconかIntel)やServBayのOpenSSLバージョンに合わせ、適切なパスを選択しましょう。トラストストアを正しく設定すれば、ローカル開発環境で外部SSL/TLSサービスと安全に通信できるようになります。