Mensagem de erro
cURL error 60: SSL certificate problem: unable to get local issuer certificate
Também aparece como
- GuzzleHttp\Exception\ConnectException: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.se/libcurl/c/libcurl-errors.html) for https://api.example.com:8443/
O Guzzle formata o erro como “cURL error 60: SSL certificate problem: <reason> (see https://curl.se/libcurl/c/libcurl-errors.html) for <url>”; versões mais antigas do Guzzle apontavam para https://curl.haxx.se/libcurl/c/libcurl-errors.html. O motivo depois de “problem:” varia (“self-signed certificate in certificate chain”, “certificate has expired”). Chamada diretamente, curl_error() retorna o texto depois de “cURL error 60: ”.
PHP — Guzzle (and Laravel HTTP client, Symfony HttpClient on curl) over ext-curl; curl_errno() 60
O que significa
A extensão curl do PHP (usada pelo Guzzle, pelo cliente HTTP do Laravel e pelo transporte curl do HttpClient do Symfony) não conseguiu verificar o certificado do servidor: erro 60 da libcurl, com o motivo do OpenSSL “unable to get local issuer certificate”. A libcurl não encontrou no bundle dela nenhuma CA que tenha emitido a cadeia do servidor. No Windows, muitas vezes o bundle nem está configurado; em outros sistemas, a CA é privada ou o servidor omite um intermediário.
Por que acontece
PHP no Windows sem bundle de CAs
comumOs builds do PHP para Windows não trazem um bundle de CAs; enquanto curl.cainfo não apontar para um (por exemplo, o cacert.pem do projeto curl), toda requisição HTTPS falha desse jeito.
Uma CA privada ou um proxy com inspeção de TLS
comumAPIs internas e conexões que passam por proxy encadeiam até raízes que não estão no bundle que a libcurl usa.
O servidor omite o intermediário
ocasionalA libcurl com OpenSSL não busca intermediários ausentes.
Imagens de contêiner sem certificados de CA (atualizados)
ocasionalImagens PHP personalizadas montadas sem o pacote ca-certificates, ou com um bundle antigo, falham até com hosts públicos.
Como corrigir
- 1.
Configurar curl.cainfo e openssl.cafile
Defina os dois no php.ini para que o ext-curl e os streams do PHP usem o mesmo bundle; lembre-se de que a CLI e o PHP-FPM muitas vezes carregam arquivos php.ini diferentes.
ini ; php.ini — CA bundle for ext-curl (Guzzle, Laravel Http, Symfony HttpClient) and for streams curl.cainfo = "/etc/ssl/certs/ca-certificates.crt" openssl.cafile = "/etc/ssl/certs/ca-certificates.crt" ; Windows: download cacert.pem from https://curl.se/docs/caextract.html and use its full path - 2.
Guzzle: passar um bundle de CAs para verify
A opção verify aceita o caminho de um arquivo de CA; o Guzzle confere se o arquivo existe e, se não existir, lança “SSL CA bundle not found”.
php // Trust a private CA for one client: verify takes a CA bundle path $client = new GuzzleHttp\Client(['verify' => '/etc/ssl/private-ca/corp-root-ca.pem']); $response = $client->get('https://api.example.com/'); - 3.
Instalar a CA no repositório do sistema
No Linux, o curl e o OpenSSL do PHP usam por padrão o bundle da distribuição; openssl_get_cert_locations() mostra os caminhos.
bash # Debian/Ubuntu-based PHP images: install the root, then check what PHP uses cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt && update-ca-certificates php -r 'var_dump(openssl_get_cert_locations()); echo ini_get("curl.cainfo"), PHP_EOL;' - 4.
Verificar qual php.ini está em vigor
O comando php --ini lista os arquivos carregados; uma configuração adicionada ao arquivo errado (CLI, FPM ou Apache) não tem efeito.
bash php --ini # which php.ini files are loaded (CLI and FPM differ!) php -i | grep -E 'curl.cainfo|openssl.cafile'
Erros relacionados
cURL error 77: error setting certificate file: /etc/ssl/missing-ca.pemPHP ext-curl (Guzzle, Laravel HTTP client)SSL operation failed with code 1. OpenSSL Error messages:PHP streams (file_get_contents, fopen, stream_socket_client, SoapClient, mail libraries)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)