OpenSSL 문제 해결: unable to get local issuer certificate
오류 수정하기
OpenSSL로 보안 연결을 할 때(예: PHP 환경에서 네트워크 요청을 실행하거나, openssl
, curl
명령을 실행하거나, Node.js 또는 Python 애플리케이션에서 SSL/TLS 연결을 시도할 때), 개발자는 종종 20:unable to get local issuer certificate
라는 오류 메시지를 접할 수 있습니다. 이 오류는 OpenSSL의 SSL 인증서 검증 방식과 관련된 매우 흔한 문제입니다.
보안을 위해, OpenSSL은 기본적으로 신뢰할 수 있는 루트 인증 기관(CA)을 명확하게 지정해야 각종 인증서 체인 검증을 수행합니다. 신뢰점이 지정되어 있지 않거나 찾을 수 없는 경우, 서버 인증서의 유효성을 확인하지 못해 해당 오류가 발생합니다.
이 문서에서는 이 오류의 원인과 ServBay 환경에서 이를 해결하는 방법을 PHP, Python, Node.js, curl
등 다양한 환경별로 상세히 안내합니다.
오류 메시지: 20:unable to get local issuer certificate
문제 설명
OpenSSL이 원격 서버의 SSL/TLS 인증서를 검증하려면, 서버 인증서부터 신뢰 가능한 루트 CA까지의 인증서 체인을 구축해야 합니다. 이때, OpenSSL이 필요로 하는 중간 인증서 또는 최종 루트 CA 인증서를 로컬에 찾지 못하거나 적절한 신뢰 저장소(CAFile 또는 CAPath)가 지정되어 있지 않으면, 검증 과정이 실패하여 20:unable to get local issuer certificate
오류가 반환됩니다.
간단히 말해서 OpenSSL이 어느 인증 기관을 신뢰해야 할지 모르기 때문에, 연결하려는 서버의 신원을 확신할 수 없습니다.
ServBay의 OpenSSL 버전 및 CA 인증서 경로
ServBay는 통합 로컬 웹 개발 환경으로, OpenSSL이 내장되어 있고 주요 공용 루트 CA 인증서도 기본으로 포함되어 있습니다. ServBay의 OpenSSL 버전은 사용자의 장치 프로세서 종류에 따라 다릅니다:
- Apple Silicon(M 시리즈 칩): OpenSSL 3.2.1 버전 사용
- Intel 칩: 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
unable to get local issuer certificate
오류를 해결하는 핵심은 OpenSSL이 신뢰할 수 있는 CA 인증서를 찾을 수 있도록 위와 같은 cafile
또는 capath
경로를 명확히 지정해주는 것입니다.
문제 해결 예시
아래는 각종 도구 및 언어 환경에서 OpenSSL CA 신뢰 저장소를 지정하는 방법의 예시입니다.
예시 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의 다양한 네트워크 기능(HTTPS로 file_get_contents
, stream_socket_client
등, cURL 확장 등)은 모두 내부적으로 OpenSSL을 사용합니다. CA 신뢰 저장소를 지정하는 방법은 다음과 같습니다.
방법 A: php.ini
수정(추천)
글로벌 적용에 가장 간편한 방법입니다. 현재 PHP 버전의 php.ini
파일을(ServBay 제어판에서 편집 가능) [openssl]
섹션에서 아래와 같이 설정합니다. 사용 중인 칩 종류에 따라 경로를 선택하세요.
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: 코드에서 직접 설정(현재 연결만 영향)
전역 설정을 원치 않을 경우, stream_context_create
로 SSL/TLS 연결 시 ssl
옵션에 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);
// 지정한 컨텍스트로 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'
# CA 파일 지정하여 기본 SSL 컨텍스트 생성
context = ssl.create_default_context(cafile=ca_cert_file)
# 또는 디렉터리 지정: 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") # ServBay 브랜드 예시 도메인 사용
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
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
예시 4: Node.js에서 OpenSSL 사용(tls 모듈)
Node.js의 tls
모듈을 활용하면 SSL/TLS 연결 시 ca
속성에 신뢰할 수 있는 CA 인증서를 지정 가능합니다. ca
는 인증서 파일 내용을 문자열이나 Buffer로 제공하면 됩니다.
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),
// tls 모듈은 기본적으로 서버 호스트명 검증(checkServerIdentity) 수행,
// CA 파일 및 서버 인증서가 올바르면 검증 통과합니다.
// 특별한 이유와 충분한 이해 없이 checkServerIdentity를 끄지 마세요.
// checkServerIdentity: () => { return null; } // <-- 이 줄은 쓰지 마세요, 중요한 보안 검증 비활성화!
};
const socket = tls.connect(options, () => {
console.log('SSL 연결 성공');
// HTTPS 요청이 필요한 경우 socket.write 사용(여기선 연결 예시만)
// 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 경로만 올바르게 지정하면 기본 검증이 성공해야 하며, 호스트명 검증 오류시에는 인증서 자체 문제일 수 있습니다.
예시 5: curl
명령어에서 OpenSSL 사용
curl
명령어도 HTTPS 요청 시 OpenSSL(또는 기타 SSL 라이브러리)를 사용합니다. --cacert
파라미터로 CA 인증서 번들 파일을 지정할 수 있습니다.
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
은 인증서 관련 오류 없이 정상적으로 내용을 받아올 수 있습니다.
요약
OpenSSL의 20:unable to get local issuer certificate
오류는 SSL/TLS 연결 시 매우 빈번하며, 원인은 신뢰할 수 있는 CA 저장소 지정이 없을 때 발생합니다. ServBay는 주요 공용 CA 루트 인증서를 포함하는 cacert.pem
파일을 미리 제공하여 개발자가 신뢰성 있는 환경을 갖출 수 있도록 합니다.
이 문제의 해결법은 개발 환경(php.ini
, 코드상 SSL 컨텍스트 옵션, 명령행의 openssl -CAfile
, curl --cacert
등)에서 ServBay의 cacert.pem
경로를 지정하는 것입니다. macOS 칩 종류(Apple Silicon, Intel) 및 ServBay OpenSSL 버전 별로 경로를 정확히 선택하세요. CA 신뢰 저장소를 올바르게 설정하면 로컬 개발 환경에서 외부 SSL/TLS 서비스와 안전한 통신을 보장할 수 있습니다.