Pular para o conteúdo
ecdsa.com
PHPPHP

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

Como corrigir

  1. 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. 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. 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. 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

Ver toda a base de erros de assinatura