Introduction
PHP cURL verifies SSL certificates by default when making HTTPS requests. When the CA certificate bundle is missing, outdated, or not properly configured, cURL fails with "SSL certificate problem: unable to get local issuer certificate". This prevents PHP from making any HTTPS requests to external APIs.
This issue is common on fresh server installations, Windows environments, and corporate networks with TLS intercepting proxies.
Symptoms
- curl_exec returns false with "SSL certificate problem: unable to get local issuer certificate"
- curl_error() returns error code 60 (CURLE_SSL_CACERT)
- Same HTTPS URL works in browser and with system curl command
Common Causes
- curl.cainfo is not set in php.ini, so cURL cannot find the CA bundle
- CA certificate bundle is missing or outdated on the system
- Corporate proxy uses a custom CA that is not in the standard bundle
Step-by-Step Fix
- 1.Download and configure the CA bundle: Point PHP to a valid CA certificate file.
- 2.```bash
- 3.# Download the latest CA bundle:
- 4.curl -o /etc/ssl/certs/ca-certificates.crt https://curl.se/ca/cacert.pem
# Configure php.ini: # /etc/php/8.2/fpm/php.ini curl.cainfo = /etc/ssl/certs/ca-certificates.crt openssl.cafile = /etc/ssl/certs/ca-certificates.crt
# Restart PHP-FPM: sudo systemctl restart php8.2-fpm ```
- 1.Set CA bundle path in code as fallback: Configure cURL options directly.
- 2.```php
- 3.<?php
- 4.$ch = curl_init('https://api.example.com/data');
curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt', // Explicit CA bundle CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, ]);
$response = curl_exec($ch); if ($response === false) { $error = curl_error($ch); // "SSL certificate problem..." $errno = curl_errno($ch); // 60 = CURLE_SSL_CACERT } curl_close($ch); ```
- 1.Never disable SSL verification in production: Use proper CA configuration instead.
- 2.```php
- 3.<?php
- 4.// WRONG - disables security, vulnerable to MITM attacks:
- 5.curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- 6.curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// RIGHT - use proper CA bundle: curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem'); ```
Prevention
- Include CA bundle download in server provisioning scripts
- Set curl.cainfo and openssl.cafile in all php.ini files (CLI, FPM, Apache)
- Monitor certificate expiration for services your application depends on
- Use Guzzle or Symfony HTTP client which handle CA bundles automatically