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
Windows PHP without a CA bundle
commonPHP builds for Windows do not ship a CA bundle; until curl.cainfo points at one (for example cacert.pem from the curl project), every HTTPS request fails this way.
A private CA or TLS-inspecting proxy
commonInternal APIs and proxied connections chain to roots that are not in the bundle libcurl uses.
The server omits its intermediate
occasionallibcurl with OpenSSL does not fetch missing intermediates.
Container images without (current) CA certificates
occasionalCustom PHP images built without ca-certificates, or with an old bundle, fail for public hosts too.
How to fix it
- 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.
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.
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.
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
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)