Skip to content
ecdsa.com
PHPPHP

Error message

cURL error 60: SSL certificate problem: unable to get local issuer certificate

Also printed as

  • 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/

Guzzle formats it as "cURL error 60: SSL certificate problem: <reason> (see https://curl.se/libcurl/c/libcurl-errors.html) for <url>"; older Guzzle releases linked https://curl.haxx.se/libcurl/c/libcurl-errors.html. The reason after "problem:" varies ("self-signed certificate in certificate chain", "certificate has expired"). Plain curl_error() returns the text after "cURL error 60: ".

PHP — Guzzle (and Laravel HTTP client, Symfony HttpClient on curl) over ext-curl; curl_errno() 60

What it means

PHP's curl extension (used by Guzzle, the Laravel HTTP client and Symfony HttpClient's curl transport) could not verify the server's certificate: libcurl error 60 with OpenSSL's reason "unable to get local issuer certificate". libcurl found no CA in its bundle that issued the server's chain. On Windows the bundle is often not configured at all; elsewhere the CA is private or the server omits an intermediate.

Why it happens

How to fix it

  1. 1.

    Configure curl.cainfo and openssl.cafile

    Set both in php.ini so ext-curl and PHP streams use the same bundle; remember that CLI and PHP-FPM often load different php.ini files.

    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: pass a CA bundle to verify

    verify accepts a path to a CA file; Guzzle checks that the file exists and raises "SSL CA bundle not found" otherwise.

    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.

    Install the CA in the system store

    On Linux, PHP's curl and OpenSSL use the distribution bundle by default; openssl_get_cert_locations() shows the paths.

    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.

    Check which php.ini is in effect

    php --ini lists the loaded files; a setting added to the wrong file (CLI vs FPM vs Apache) has no effect.

    bash
    php --ini                        # which php.ini files are loaded (CLI and FPM differ!)
    php -i | grep -E 'curl.cainfo|openssl.cafile'

Related errors

Browse the full signature error database