Solución de problemas de OpenSSL: Resolución del error unable to get local issuer certificate
Al utilizar OpenSSL para conexiones seguras (por ejemplo, al realizar solicitudes de red en PHP, ejecutando comandos openssl
o curl
, o estableciendo conexiones SSL/TLS en aplicaciones Node.js/Python), los desarrolladores pueden encontrarse con el mensaje de error 20:unable to get local issuer certificate
. Este es un problema común y heredado, relacionado con el modo en que OpenSSL valida los certificados de las contrapartes.
Por razones de seguridad, OpenSSL exige por defecto conocer explícitamente la autoridad certificadora raíz (CA) de confianza para validar una cadena de certificados. Si estas entidades de confianza no se encuentran o no se especifican, OpenSSL no puede confirmar la validez del certificado del servidor y muestra este error.
En este artículo se explican detalladamente las causas del error y se presentan soluciones específicas para el entorno ServBay, abarcando la configuración del almacén de confianza de OpenSSL en PHP, Python, Node.js y el comando curl
.
Mensaje de error 20:unable to get local issuer certificate
Descripción del problema
Cuando OpenSSL intenta validar el certificado SSL/TLS de un servidor remoto, construye una cadena de certificados desde el del servidor hasta una CA raíz de confianza. Si OpenSSL no puede encontrar localmente algún certificado intermedio necesario o la CA raíz final, o no se ha establecido una ruta adecuada de confianza (CAFile o CAPath), la validación falla y se devuelve el error 20:unable to get local issuer certificate
.
En resumen, OpenSSL no sabe en qué autoridad certificadora confiar y, por tanto, no puede confirmar la identidad del servidor al que intenta conectarse.
Versión de OpenSSL y ruta de certificados CA en ServBay
ServBay, como entorno local de desarrollo web integrado, incluye OpenSSL y empaqueta certificados raíz CA públicos habituales para facilitar su uso. La versión de OpenSSL en ServBay depende del tipo de chip de tu Mac:
- ServBay para Apple Silicon (chips serie M): usa OpenSSL versión 3.2.1.
- ServBay para Intel: usa OpenSSL versión 1.1.1u.
Los archivos de certificados CA (cacert.pem
) y el directorio de certificados (certs
) correspondientes a cada versión de OpenSSL están ubicados en la ruta de instalación del paquete ServBay. Busca la ubicación correcta según tu versión:
ini
# Archivo empaquetado con todos los CA raíz de confianza
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Directorio con archivos individuales de certificados (usualmente cacert.pem es suficiente, pero algunos entornos pueden requerir capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# Archivo empaquetado con todos los CA raíz de confianza
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Directorio con archivos individuales de certificados
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
La clave para resolver el error unable to get local issuer certificate
es especificar de forma explícita la ruta de cafile
o capath
donde sea necesario usar OpenSSL, indicando así a OpenSSL dónde buscar las CA de confianza.
Ejemplos de soluciones
A continuación, ejemplos de cómo especificar el almacén de confianza de CA de OpenSSL en diferentes herramientas y lenguajes.
Ejemplo 1: Pruebas de conexión con el comando openssl
Si usas directamente el comando openssl s_client
y te aparece el error:
bash
openssl s_client -quiet -connect gmail.com:443
1
Puedes ver una salida con el error 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
# ... otra información de conexión ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Solución:
Utiliza el parámetro -CAfile
para indicar explícitamente la ruta del archivo de CA proporcionado por ServBay:
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
Tras una conexión y validación exitosa, la salida mostrará verify return:1
y ya no aparecerá la línea 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
# ... otra información de conexión ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Ejemplo 2: Uso de OpenSSL en PHP
Muchas funciones de red de PHP (como file_get_contents
en URLs HTTPS, stream_socket_client
para conexiones SSL o la extensión cURL) dependen de OpenSSL. Puedes especificar el almacén de CA mediante la configuración en php.ini
o dentro del propio código.
Método A: Modificar php.ini
(recomendado)
Esta es la solución global más sencilla. Edita el archivo php.ini
correspondiente a tu versión de PHP (puedes localizarlo desde el panel de ServBay), busca la sección [openssl]
y añade/modifica las siguientes líneas especificando openssl.cafile
y openssl.capath
según tu tipo de chip:
ini
[openssl]
; Especifica el archivo cacert de confianza
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Especifica el directorio de certificados CA (opcional, recomendado)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Especifica el archivo cacert de confianza
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Especifica el directorio de certificados CA (opcional, recomendado)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Tras modificar php.ini
, reinicia el servicio de PHP en ServBay (o todo ServBay) para aplicar los cambios.
Método B: Configurar en el código (afecta solo a la conexión actual)
Si prefieres no modificar globalmente el php.ini
, establece la opción cafile
en el contexto ssl
al crear la conexión:
php
<?php
// Ejemplo: conectar a un servidor SMTP con SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Elige la ruta correcta de cacert según tu versión de ServBay
// Para Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Para Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Habilita verificación del certificado del servidor
'verify_peer_name' => true, // Verifica que el nombre del certificado coincida con el host
'allow_self_signed' => false, // No permite certificados autofirmados, salvo si confías explícitamente en ellos
'cafile' => $caCertFile, // Archivo cacert de confianza
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Opcional: directorio CA
],
];
$context = stream_context_create($contextOptions);
// Establece la conexión SSL/TLS usando el contexto creado
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // Timeout de conexión
STREAM_CLIENT_CONNECT,
$context // Pasa las opciones de contexto
);
if ($connection) {
echo "Conexión establecida a $server:$port\n";
// Ejemplo: enviar comando EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Usando un dominio de ejemplo relacionado con ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "No se pudo conectar a $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
Ejemplo 3: Uso de OpenSSL en Python (módulo ssl)
El módulo ssl
de Python permite crear contextos SSL/TLS y especificar el archivo CA confiable.
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Selecciona la ruta de CA correcta según tu versión de ServBay
# Para Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Para Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Crea un contexto SSL por defecto especificando el archivo cacert
context = ssl.create_default_context(cafile=ca_cert_file)
# También puedes usar un directorio: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Establece una conexión socket normal
with socket.create_connection((server, port)) as sock:
# Envuélvela en un socket SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"Conexión SSL establecida. Protocolo negociado: {ssock.version()}")
# Ejemplo: enviar comando EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Dominio de ejemplo ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"No se pudo conectar o error 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
Ejemplo 4: Uso de OpenSSL en Node.js (módulo tls)
En Node.js, el módulo tls
se utiliza para conexiones seguras. Indica el CA de confianza mediante la opción ca
, leyendo el contenido del archivo cacert.pem
de ServBay.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Cambia a un sitio web de confianza estándar como ejemplo
const port = 443;
// Selecciona la ruta del archivo cacert adecuado según tu ServBay
// Para Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Para Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Lee el archivo CA
ca: fs.readFileSync(caCertFile),
// El módulo tls de Node.js verifica el nombre del servidor por defecto,
// Si el archivo CA está correctamente indicado y el certificado es válido, la verificación tendrá éxito.
// No deshabilites checkServerIdentity a menos que sepas exactamente qué haces.
// checkServerIdentity: () => { return null; } // <-- ¡No uses esto salvo que entiendas los riesgos!
};
const socket = tls.connect(options, () => {
console.log('Conexión SSL establecida');
// Para conexiones HTTPS normalmente enviarías una solicitud HTTP, aquí solo demostramos la conexión.
// 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('Conexión cerrada');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // Imprime mensajes de error
});
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
Nota: En el ejemplo de Node.js se ha eliminado checkServerIdentity: () => { return null; }
. Esta opción desactiva la verificación del nombre del host en el servidor, lo cual es muy inseguro. El error de OpenSSL unable to get local issuer certificate
está relacionado con el almacén de CA, no con la comprobación del nombre del host (verify_peer_name
). Si especificas correctamente la opción ca
y el certificado del servidor es válido, la verificación por defecto en Node.js funcionará. Si el error es por el nombre del host, normalmente se trata de un problema con el certificado, no con el almacén CA.
Ejemplo 5: Uso de CA con el comando curl
El comando curl
también utiliza OpenSSL (u otra biblioteca SSL) para solicitudes HTTPS. Usa el parámetro --cacert
para indicar el archivo de CA.
bash
# Usa el archivo de certificados CA de ServBay para acceder a un sitio HTTPS
# Elige la ruta adecuada según tu versión de ServBay
# Para Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Para 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
Si la ruta del CA es correcta y el certificado del servidor es válido, curl
accederá al sitio sin errores de validación de certificado.
Resumen
El error 20:unable to get local issuer certificate
es muy común al trabajar con OpenSSL en conexiones SSL/TLS. Su causa raíz es la necesidad de un almacén de confianza explícito para validar los certificados del servidor. ServBay proporciona a los desarrolladores un archivo cacert.pem
preconfigurado con CA raíces públicas habituales.
La solución consiste en indicar la ruta del archivo cacert.pem
de ServBay en la configuración de tu entorno de desarrollo (php.ini
, opciones en el contexto SSL en el código, parámetros de línea de comandos como -CAfile
en openssl
o --cacert
en curl
). Asegúrate de elegir el archivo adecuado según tu arquitectura macOS (Apple Silicon o Intel) y la versión de OpenSSL de ServBay correspondiente. Una configuración correcta del almacén de confianza CA te permitirá conectar tu entorno de desarrollo local de forma segura con servicios SSL/TLS externos.