OpenSSL Troubleshooting: Fixing the unable to get local issuer certificate
Error
When working with secure connections via OpenSSL—such as making network requests in PHP, running openssl
or curl
commands, or establishing SSL/TLS connections in Node.js or Python—you may encounter the error message 20:unable to get local issuer certificate
. This is a common legacy issue related to how OpenSSL validates peer certificates.
For security reasons, OpenSSL requires explicit knowledge of trusted Certificate Authorities (CA) when validating a certificate chain. If the necessary trust anchors are either missing or not specified, OpenSSL cannot verify the legitimacy of the server certificate, resulting in this error.
This guide explains the underlying causes of this error and provides ServBay-specific solutions, including how to configure OpenSSL’s trust store for PHP, Python, Node.js, and the curl
command.
Error Message: 20:unable to get local issuer certificate
Problem Overview
When OpenSSL tries to validate a remote server’s SSL/TLS certificate, it constructs a certificate chain from the server certificate up to a trusted root CA. If OpenSSL cannot locate any of the required intermediate or root CA certificates locally, or cannot find a configured trust store (CAFile or CAPath) to search for those certificates, the verification fails and you receive the 20:unable to get local issuer certificate
error.
In simpler terms, OpenSSL does not know which certificate authority to trust and therefore cannot confirm the identity of the server it’s connecting to.
OpenSSL Versions and CA Certificate Paths in ServBay
ServBay is an integrated local web development environment that comes prepackaged with OpenSSL and commonly used public root CA certificates, simplifying development setup. The OpenSSL version used by ServBay depends on your device’s architecture:
- Apple Silicon (M Series) ServBay: OpenSSL version 3.2.1
- Intel ServBay: OpenSSL version 1.1.1u
The related CA certificate file (cacert.pem
) and certificate directory (certs
) are located within the ServBay installation. The exact paths depend on your platform and architecture:
ini
# Bundle file containing all trusted root certificates
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Directory with standalone certificate files (usually not needed, but required for certain apps)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Bundle file containing all trusted root certificates
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Directory with standalone certificate files
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# Bundle file containing all trusted root certificates
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# Directory with standalone certificate files
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
The key to resolving the unable to get local issuer certificate
error is to explicitly tell OpenSSL where to locate the trusted CA certificates by specifying the above cafile
or capath
path in your configuration or code.
Solution Examples
Here’s how to specify the OpenSSL CA trust store across different languages and tools.
Example 1: Using the openssl
Command for Connectivity Testing
If you’re using the openssl s_client
command and encounter an error like the following:
bash
openssl s_client -quiet -connect gmail.com:443
1
You might see output similar to this, including the verify error:num=20:unable to get local issuer certificate
line:
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
# ... other connection info ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Solution:
Use the -CAfile
parameter to explicitly point to ServBay’s bundled CA certificate file:
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
After a successful connection and certificate verification, the output’s verify return
value will be 1
with no verify error:num=20
line:
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
# ... other connection info ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Example 2: Using OpenSSL in PHP
Many PHP networking features—such as file_get_contents
for HTTPS URLs, stream_socket_client
for SSL connections, and the cURL extension—depend on OpenSSL. You can specify a trusted CA store via modifications to php.ini
or by setting stream context options in your code.
Method A: Update php.ini
(Recommended)
For a global, hassle-free fix, edit the php.ini
file for your current PHP version (accessible via the ServBay control panel). Find or add the [openssl]
section, specifying openssl.cafile
and openssl.capath
. Choose the correct paths based on your chip architecture.
ini
[openssl]
; Specify the trusted CA bundle file
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Specify the directory containing CA certificates (optional but recommended)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Specify the trusted CA bundle file
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Specify the directory containing CA certificates (optional but recommended)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Restart the PHP service in ServBay (or all of ServBay) to apply the changes.
Method B: Set Options in Code (Affects Only Current Connection)
If you wish not to make global changes, specify the cafile
with the stream_context_create
function when making SSL/TLS connections.
php
<?php
// Example: Connect to an SSL/TLS-enabled SMTP server
$server = 'ssl0.ovh.net';
$port = 465;
// Use the correct CA file path for your ServBay version
// For Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// For Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Enable peer certificate verification
'verify_peer_name' => true, // Verify hostname matches certificate
'allow_self_signed' => false, // Don't allow self-signed certificates unless explicitly trusted
'cafile' => $caCertFile, // Specify CA bundle file
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Optional: specify CA directory
],
];
$context = stream_context_create($contextOptions);
// Establish SSL/TLS connection with the provided context
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Connection timeout
STREAM_CLIENT_CONNECT,
$context // Pass context options
);
if ($connection) {
echo "Connection established to $server:$port\n";
// Example: send EHLO command
fwrite($connection, "EHLO servbay.demo\r\n"); // Using ServBay branded sample domain
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Failed to connect to $server:$port. Error: $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
47
48
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
Example 3: Using OpenSSL in Python (ssl Module)
Python’s ssl
module lets you create SSL/TLS contexts and specify trusted CA certificates.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Use the correct CA file path for your ServBay version
# For Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# For Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Create a default SSL context using the CA file
context = ssl.create_default_context(cafile=ca_cert_file)
# Optionally, use a directory: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Establish a regular socket connection
with socket.create_connection((server, port)) as sock:
# Wrap the socket with SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Example: send EHLO command
ssock.sendall(b"EHLO servbay.demo\r\n") # Using ServBay branded sample domain
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}")
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
Example 4: Using OpenSSL in Node.js (tls Module)
Node.js’s tls
module lets you set up TLS/SSL connections, specifying trusted CA certificates via the ca
property. The simplest way is to read ServBay’s cacert.pem
file:
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Use a well-known, trusted website for this example
const port = 443;
// Use the correct CA file path for your ServBay version
// For Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// For Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Read in the CA certificates
ca: fs.readFileSync(caCertFile),
// Node.js's tls module validates the hostname (checkServerIdentity) by default.
// If the CA file is set correctly and the server certificate is valid, this should succeed.
// Do not disable checkServerIdentity unless you fully understand the risks.
// checkServerIdentity: () => { return null; } // <-- This disables important security checks; do NOT use unless absolutely necessary!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// For HTTPS, you’d typically send an HTTP request here, but this is just a connection example
// 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); // Print error messages
});
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
Note: The Node.js example omits checkServerIdentity: () => { return null; }
. That option disables hostname validation and is extremely insecure. The OpenSSL unable to get local issuer certificate
error is about CA trust, not hostname verification (verify_peer_name
). By correctly setting the ca
option and using valid server certificates, Node.js’s default hostname verification will succeed. Hostname verification errors typically indicate certificate problems, not CA trust store issues.
Example 5: Using the curl
Command with OpenSSL
The curl
command also uses OpenSSL (or other SSL libraries) for HTTPS requests. Use the --cacert
parameter to specify the CA bundle file.
bash
# Access an HTTPS site with ServBay’s CA certificate file
# Use the correct path for your ServBay version
# For Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# For Intel:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
1
2
3
4
5
6
7
2
3
4
5
6
7
If the CA certificate path is correct and the server certificate is valid, curl
will fetch content successfully without certificate verification errors.
Summary
The 20:unable to get local issuer certificate
error is a common issue when using OpenSSL for SSL/TLS connections. The root cause is OpenSSL not having a specified trust store for verifying server certificates. ServBay provides a ready-to-use cacert.pem
bundle containing popular public root CA certificates.
To resolve this error, always specify the path to ServBay’s cacert.pem
file in your development environment—whether in php.ini
, SSL context options in code, or command-line flags like OpenSSL’s -CAfile
or curl’s --cacert
. Make sure to use the correct path for your macOS architecture (Apple Silicon or Intel) and the associated ServBay OpenSSL version. By configuring the CA trust store properly, you ensure your local development environment can securely communicate with external SSL/TLS services.