Solução de Problemas OpenSSL: Corrigindo o erro unable to get local issuer certificate
Ao utilizar o OpenSSL para conexões seguras (por exemplo, realizando requisições de rede em ambientes PHP, executando comandos openssl
ou curl
, ou estabelecendo conexões SSL/TLS em aplicações Node.js/Python), desenvolvedores podem se deparar com o erro 20:unable to get local issuer certificate
. Esse é um problema recorrente, herdado de como o OpenSSL valida os certificados das partes remotas.
Por motivos de segurança, o OpenSSL exige saber exatamente quais autoridades certificadoras (CAs) são confiáveis ao verificar a cadeia de certificados. Se não conseguir localizar ou identificar essas âncoras de confiança, não será possível validar a legitimidade do certificado do servidor, resultando nesse erro.
Neste guia, você entenderá a origem desse erro e como resolvê-lo no ambiente ServBay, incluindo como configurar o armazenamento de confiança do OpenSSL para PHP, Python, Node.js e comandos curl
.
Entendendo o erro 20:unable to get local issuer certificate
Descrição do Problema
Quando o OpenSSL tenta validar o certificado SSL/TLS de um servidor remoto, ele constrói uma cadeia de confiança do certificado do servidor até uma CA raiz confiável. Caso o OpenSSL não encontre localmente algum dos certificados intermediários ou da raiz, ou se um armazenamento apropriado de confiança (CAFile ou CAPath) não estiver configurado, a validação falha e retorna o erro 20:unable to get local issuer certificate
.
Resumindo: o OpenSSL não sabe em qual autoridade certificadora confiar e, por isso, não pode garantir que a identidade do servidor é legítima.
Versão do OpenSSL e Caminhos dos Certificados CA no ServBay
O ServBay, como um ambiente local completo para desenvolvimento web, já inclui o pacote OpenSSL e um conjunto das principais CAs públicas para facilidade de uso. A versão do OpenSSL utilizada depende do tipo de chip do seu computador:
- ServBay para Apple Silicon (chips série M): utiliza OpenSSL versão 3.2.1.
- ServBay para Intel: utiliza OpenSSL versão 1.1.1u.
Os arquivos de certificados CA (cacert.pem
) e o diretório de certificados (certs
) correspondentes ficam na pasta de instalação dos pacotes do ServBay. Use o caminho correto conforme seu caso:
# Arquivo bundle com todos os certificados raiz confiáveis
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Diretório com arquivos de certificados individuais (geralmente o cacert.pem já é suficiente, mas alguns aplicativos podem exigir capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
# Arquivo bundle com todos os certificados raiz confiáveis
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Diretório com arquivos de certificados individuais
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
O segredo para eliminar o erro unable to get local issuer certificate
é informar ao OpenSSL o caminho do cafile
ou capath
, para que ele saiba onde buscar as CAs confiáveis.
Exemplos de Solução
Abaixo, veja como especificar o armazenamento CA do OpenSSL em diferentes ferramentas e linguagens.
Exemplo 1: Testando conexão via comando openssl
Se ao usar o comando openssl s_client
para testar uma conexão você encontrar o erro:
openssl s_client -quiet -connect gmail.com:443
Você verá uma saída semelhante a esta, contendo verify error:num=20:unable to get local issuer certificate
:
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
# ... demais informações de conexão ...
2
3
4
5
6
7
8
Como corrigir:
Use o parâmetro -CAfile
para indicar explicitamente o caminho do arquivo CA fornecido pelo ServBay:
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
Se a verificação for bem-sucedida, o valor verify return
será 1
e a linha verify error:num=20
não aparecerá:
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
# ... demais informações de conexão ...
2
3
4
5
6
7
Exemplo 2: Usando OpenSSL no PHP
Várias funções de rede do PHP (por exemplo, file_get_contents
para URLs HTTPS, stream_socket_client
para conexões SSL, extensão cURL etc.) dependem do OpenSSL. Para corrigir o erro, ajuste o arquivo php.ini
ou informe o caminho do CA diretamente no código.
Método A: Editando o php.ini
(Recomendado)
Solução global mais prática. Abra o arquivo php.ini
da versão PHP ativa no ServBay (acesse pelo Painel de Controle do ServBay), localize a seção [openssl]
e adicione/ajuste os parâmetros abaixo, escolhendo o caminho de acordo com seu chip:
[openssl]
; Informar o arquivo bundle de CA confiável
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Informar o diretório de certificados CA (opcional, mas recomendado)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
5
[openssl]
; Informar o arquivo bundle de CA confiável
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Informar o diretório de certificados CA (opcional, mas recomendado)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
5
Após salvar o php.ini
, reinicie o serviço PHP (ou todo o ServBay) para que as alterações tenham efeito.
Método B: Definindo no código (Impacto somente na conexão atual)
Caso não queira modificar o php.ini
, defina o caminho do CA ao criar o contexto SSL na função stream_context_create
:
<?php
// Exemplo: conectando a um servidor SMTP via SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Escolha o caminho correto conforme sua versão 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, // Ativar verificação do certificado do peer
'verify_peer_name' => true, // Checar se o hostname do certificado bate com o host conectado
'allow_self_signed' => false, // Não permitir certificados autoassinados, a não ser que seja algo seguro para você
'cafile' => $caCertFile, // Caminho do bundle de CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Opcional, diretório de CAs
],
];
$context = stream_context_create($contextOptions);
// Estabelece a conexão SSL/TLS com o contexto criado
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30, // tempo limite
STREAM_CLIENT_CONNECT,
$context // passa as opções do contexto
);
if ($connection) {
echo "Conexão estabelecida com $server:$port\n";
// Exemplo: enviar comando EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Domínio demonstrativo ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Falha ao conectar em $server:$port. Erro: $errstr ($errno)\n";
}
?>
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
Exemplo 3: Usando OpenSSL no Python (módulo ssl)
Com o módulo ssl
do Python, basta criar um contexto SSL personalizado e informar o caminho do CA:
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Escolha o caminho correto conforme sua versão 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'
# Crie o contexto SSL indicando o arquivo CA
context = ssl.create_default_context(cafile=ca_cert_file)
# Também pode usar capath: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Criar conexão socket padrão
with socket.create_connection((server, port)) as sock:
# Transformar em socket SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"Conexão SSL estabelecida. Protocolo negociado: {ssock.version()}")
# Exemplo: enviar comando EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Domínio demostrativo ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Falha na conexão ou erro SSL: {e}")
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
Exemplo 4: Usando OpenSSL no Node.js (módulo tls)
O módulo tls
do Node.js permite passar o CA diretamente nas opções de conexão. O parâmetro ca
pode ser uma string ou Buffer contendo um ou vários certificados — o método mais simples é ler o arquivo cacert.pem
do ServBay:
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Use um site padrão e confiável para exemplo
const port = 443;
// Escolha o caminho correto conforme sua versão 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,
// Lê o conteúdo do arquivo CA
ca: fs.readFileSync(caCertFile),
// O Node.js por padrão verifica o hostname (checkServerIdentity),
// se o CA está correto e o certificado do servidor é válido, a conexão será estabelecida.
// Não desative checkServerIdentity a menos que esteja ciente dos riscos!
// checkServerIdentity: () => { return null; } // <-- Não utilize esta linha, pois desativa validação crucial de segurança!
};
const socket = tls.connect(options, () => {
console.log('Conexão SSL estabelecida');
// Normalmente, para HTTPS, deve-se enviar uma requisição HTTP. Aqui é apenas um exemplo de conexão.
// 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('Conexão fechada');
});
socket.on('error', (error) => {
console.error('Erro:', error.message); // Exibe mensagem de erro
});
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
Atenção: O exemplo do Node.js não inclui mais a opção checkServerIdentity: () => { return null; }
. Essa opção desabilita a verificação do hostname do servidor, resultando em um cenário inseguro. O erro OpenSSL unable to get local issuer certificate
refere-se ao armazenamento de confiança, não à verificação de hostname (verify_peer_name
). Informe corretamente o parâmetro ca
e mantenha a validação padrão do Node.js para garantir segurança. Se houver erro de hostname, possivelmente o certificado está incorreto — não é problema do CA.
Exemplo 5: Usando OpenSSL com o comando curl
O comando curl
utiliza OpenSSL (ou outra biblioteca SSL) para requisições HTTPS. Informe o arquivo CA via parâmetro --cacert
:
# Acessando site HTTPS com o arquivo CA do ServBay
# Use o caminho correto conforme sua versão do 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
2
3
4
5
6
7
Com as configurações certas e um certificado válido no servidor, o curl
conseguirá acessar o conteúdo sem reportar erros de validação.
Conclusão
O erro 20:unable to get local issuer certificate
é frequente ao se conectar via SSL/TLS utilizando OpenSSL e acontece quando não se especifica um arquivo de confiança para validação do certificado do servidor. O ServBay oferece o arquivo cacert.pem
com as CAs públicas mais comuns já pré-configuradas.
Basta apontar o caminho do cacert.pem
do ServBay no ambiente de desenvolvimento (seja no php.ini
, nas opções do contexto SSL em código, ou via parâmetro em comandos como openssl
ou curl
). Atenção para selecionar o arquivo correto conforme seu processador (Apple Silicon ou Intel) e versão correspondente do OpenSSL. Com o CA corretamente configurado, será possível estabelecer conexões seguras entre o ambiente local e serviços SSL/TLS externos.