Mensagem de erro
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
Também aparece como
- curl: (60) SSL certificate problem: self signed certificate in certificate chain
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
O OpenSSL 3.0 e posteriores imprimem “self-signed”; o OpenSSL 1.1.1 imprime “self signed”, sem hífen, por isso as duas grafias aparecem em logs e resultados de busca. Versões mais novas do curl (8.22.0 no nosso teste) formulam assim: “SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)”. Builds do curl com Schannel no Windows relatam o caso como “SEC_E_UNTRUSTED_ROOT”.
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
O que significa
O curl percorreu a cadeia enviada pelo servidor até um certificado raiz autoassinado, e essa raiz não está no bundle de CAs do curl. Ao contrário de “unable to get local issuer certificate”, aqui a cadeia está completa: o curl enxerga a raiz, só não tem motivo para confiar nela. Na prática, a raiz quase sempre pertence a uma CA da empresa ou a um proxy que intercepta o TLS.
Por que acontece
Um proxy ou antivírus com inspeção de TLS reassina as conexões
comumZscaler, Netskope, Fortinet, Palo Alto e muitos antivírus fazem a terminação do TLS e apresentam certificados emitidos pela própria raiz, que eles enviam na cadeia. O navegador confia nessa raiz por meio do sistema operacional, mas o curl dentro do WSL, do Docker, do Git Bash ou de um runner de CI lê o próprio bundle e a rejeita. O emissor do certificado traz o nome do fornecedor do proxy ou da sua empresa, e não o da CA real do site.
O serviço usa uma raiz privada e a envia na cadeia
comumServiços internos, ingresses do Kubernetes e appliances costumam usar um certificado de uma raiz da empresa ou criada por conta própria e incluem essa raiz na cadeia servida. Enviar a raiz não a torna confiável: uma âncora de confiança precisa já estar no cliente.
A raiz privada foi instalada em outro repositório
ocasionalA raiz foi adicionada ao repositório de certificados do Windows, ao chaveiro do macOS ou ao cacerts do Java, mas este curl lê um bundle do OpenSSL, como /etc/ssl/certs/ca-certificates.crt ou a cópia distribuída com o Git for Windows, o Homebrew ou o conda.
O trust store de raízes públicas do cliente está desatualizado
raraEm casos raros, o servidor envia uma raiz pública recente que falta em um cliente antigo, nunca atualizado. Atualizar o pacote ca-certificates resolve; se a raiz for de fato pública, outras máquinas vão verificar o site sem problemas.
Como corrigir
- 1.
Descobrir de quem é a raiz
Examine o último certificado da cadeia. Se o titular for um proxy ou um fornecedor de segurança, ou o nome da sua empresa, a correção é confiar nessa raiz nesta máquina. Se for um nome inesperado em uma rede que você não controla, pare: alguém está interceptando a conexão.
bash # Print the subject and issuer of every certificate the server (or the proxy) sends openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last entry has s: == i: — that self-signed root is what curl does not trust. - 2.
Adicionar a raiz da empresa ou do proxy ao repositório do sistema
Obtenha o certificado raiz com o departamento de TI (ou exporte-o do trust store de uma máquina gerenciada) e instale-o no bundle do sistema operacional que o curl usa. Contêineres precisam dele durante o build; o WSL precisa dele dentro da distribuição Linux, e não só no Windows.
bash # Debian, Ubuntu, Alpine sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # RHEL, Fedora, Amazon Linux sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust - 3.
Confiar na raiz para um comando ou um ambiente
--cacert e CURL_CA_BUNDLE substituem o bundle padrão do curl, então aponte-os para um arquivo que contenha tanto as raízes públicas quanto a privada quando o mesmo processo também se comunica com sites públicos.
bash curl --cacert corp-root-ca.pem https://api.example.com/ # Keep public roots too: build one bundle with both cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem export CURL_CA_BUNDLE=/opt/certs/bundle.pem - 4.
Donos do servidor: não depender do envio da raiz
Sirva o certificado folha e os intermediários e distribua a raiz privada aos clientes pelo gerenciamento de configuração, em vez de enviá-la. Incluir a raiz desperdiça bytes e nunca torna confiável uma raiz não confiável.
bash # Serve leaf + intermediates only; clients must already have the root cat api.example.com.crt intermediate.crt > fullchain.pem - 5.
Diagnóstico temporário: ver o emissor com a verificação desligada
A opção -k desativa as verificações de certificado para que a saída detalhada mostre o titular e o emissor que o servidor apresenta. Use-a só para identificar a raiz. Esquecida em scripts, a opção -k permite que qualquer um no caminho leia e altere o tráfego, inclusive tokens e instaladores baixados.
bash # Diagnostic only: shows who issued the certificate even though curl distrusts it curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'
Erros relacionados
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)