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/certs1
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/certs1
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\certs1
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:4431
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.pem1
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem1
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/certs1
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/certs1
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.com1
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.
