Mensagem de erro
curl: (60) SSL certificate problem: unable to get local issuer certificate
Também aparece como
- curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
- SSL certificate problem: unable to get local issuer certificate
O curl baseado em OpenSSL (Linux, Homebrew no macOS, Git for Windows) imprime este texto; versões mais novas (8.22.0 no nosso teste, enquanto a 8.14.1 ainda usava o texto acima) formulam assim: “SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)”, acrescentando o código de verificação do OpenSSL. O curl compilado com Schannel no Windows relata a mesma situação como “schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)”, e a libcurl baseada em GnuTLS (usada pelo git no Debian e no Ubuntu), como “server verification failed: certificate signer not trusted”. Ferramentas baseadas na libcurl, como PHP e git, imprimem a parte depois de “(60) ” com o próprio prefixo.
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
O que significa
O curl recebeu os certificados do servidor, mas não encontrou, no próprio bundle de CAs, o certificado que emitiu o último da cadeia. O certificado pode ser perfeitamente válido; o curl simplesmente não consegue ligá-lo a nenhuma raiz em que confia, por isso recusa a conexão (código de saída 60). A peça que falta é um intermediário que o servidor esqueceu de enviar ou uma CA raiz que sua máquina não tem.
Por que acontece
O servidor envia só o próprio certificado
comumCAs públicas assinam certificados de servidor com um intermediário, e o servidor precisa enviar esse intermediário junto com o certificado folha. Um servidor configurado com cert.pem em vez de fullchain.pem funciona em navegadores de desktop, que armazenam intermediários em cache e buscam os que faltam, mas o curl não faz nenhuma das duas coisas e para no certificado folha.
O certificado vem de uma CA privada ou corporativa
comumServiços internos, ambientes de homologação e ingresses do Kubernetes costumam usar certificados de uma CA da empresa ou criada por conta própria. Essa raiz não está no bundle público que o curl usa (/etc/ssl/certs/ca-certificates.crt no Debian e no Ubuntu), então nada do que ela emitiu pode ser verificado até que a raiz seja instalada.
Um proxy com inspeção de TLS reassina o tráfego
ocasionalProxies corporativos e produtos de segurança de endpoint (Zscaler, Netskope, Palo Alto e similares) substituem o certificado de cada site por um emitido pela própria CA. O navegador confia nessa CA pelo repositório de certificados do sistema operacional, enquanto o curl, os contêineres e o WSL usam um bundle separado que não a contém, por isso toda chamada HTTPS falha do mesmo jeito.
A máquina não tem um bundle de CAs utilizável
ocasionalImagens de contêiner slim e distroless vêm sem o pacote ca-certificates, e sistemas antigos carregam bundles anteriores às raízes atuais. Com um bundle vazio ou desatualizado, até certificados de sites públicos conhecidos falham com esta mensagem.
A CA foi adicionada a um repositório que este curl não lê
raraCada build do curl lê um bundle específico: o curl do Homebrew, um ambiente conda e o Git for Windows trazem cada um o seu, e o Windows tem builds com Schannel e com OpenSSL. Uma CA adicionada ao repositório de certificados do Windows ou ao chaveiro do macOS só chega aos builds que leem esse repositório.
Como corrigir
- 1.
Ver o que o servidor envia de fato
Liste os certificados do handshake. Se aparecer só um (profundidade 0) e o emissor dele for uma CA intermediária, a configuração do servidor está incompleta e precisa ser corrigida no servidor. Se a cadeia terminar em uma raiz da empresa ou criada por conta própria, é o cliente que não tem essa raiz.
bash # Which certificates does the server send, and who issued the last one? openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # 0 s:CN=api.example.com # i:C=US, O=Example Trust Services, CN=Example TLS Issuing CA <- leaf only: intermediate missing - 2.
Corrigir o servidor: servir a cadeia completa
Configure o servidor web com um bundle que contenha o certificado folha seguido de todos os intermediários. Isso corrige o erro para todos os clientes de uma vez, inclusive aplicativos móveis e clientes de API que você não controla.
bash # Leaf first, then each intermediate in order; the root itself is optional cat api.example.com.crt intermediate.crt > fullchain.pem # nginx: ssl_certificate /etc/nginx/tls/fullchain.pem; # Apache: SSLCertificateFile /etc/apache2/tls/fullchain.pem # certbot: use live/<name>/fullchain.pem, never cert.pem - 3.
Instalar uma CA raiz privada no repositório do sistema
Quando a cadeia termina legitimamente em uma raiz da empresa ou interna, adicione essa raiz ao bundle do sistema operacional. Obtenha a raiz com a equipe de TI ou de plataforma, ou com a própria CA; nunca a partir do certificado que a conexão com falha apresentou, já que é exatamente nele que você ainda não pode confiar.
bash # Debian, Ubuntu, Alpine (the file must end in .crt) 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 - 4.
Apontar o curl para um bundle em um comando ou em um shell
A opção --cacert adiciona confiança para uma única chamada sem mexer no sistema; a variável CURL_CA_BUNDLE define o bundle para todas as chamadas do curl no ambiente. O arquivo pode conter vários certificados PEM.
bash curl --cacert corp-root-ca.pem https://api.example.com/ # or for every curl command in this shell (a file may hold several PEM certificates) export CURL_CA_BUNDLE=/opt/certs/corp-and-public-roots.pem - 5.
Instalar o bundle de CAs em contêineres mínimos
Se até sites públicos falham dentro de um contêiner, a imagem não tem bundle. Instale ca-certificates na imagem (ou copie /etc/ssl/certs/ca-certificates.crt de um estágio de build) em vez de desligar a verificação.
bash # Debian/Ubuntu-based images apt-get update && apt-get install -y --no-install-recommends ca-certificates # Alpine apk add --no-cache ca-certificates - 6.
Diagnóstico temporário: confirmar que é um problema de confiança
A opção -k (--insecure) desativa a verificação de certificados, então uma resposta bem-sucedida só prova que o caminho de rede funciona e que a falha é de confiança. Nunca a deixe em scripts: com -k, qualquer um no caminho pode ler e alterar o tráfego, inclusive credenciais e código baixado.
bash # Diagnostic only: if this works, the problem is trust, not the network. curl -sS -k -o /dev/null -w '%{http_code}\n' https://api.example.com/
Erros relacionados
curl: (60) SSL certificate problem: self-signed certificate in certificate chaincurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=20:unable to get local issuer certificateOpenSSL CLI (openssl s_client / openssl verify)fatal: unable to access 'https://git.example.com/team/app.git/': SSL certificat…git over HTTPS with libcurl + OpenSSL (Git for Windows default, macOS Homebrew, Alpine, RHEL)x509: certificate signed by unknown authorityGo crypto/x509