What's Actually Happening

Your PHP application using cURL fails to connect to HTTPS endpoints because SSL certificate verification fails. This occurs when cURL cannot validate the server's SSL certificate against trusted Certificate Authority (CA) certificates, when the CA certificate bundle is missing or outdated, when the server certificate is expired or invalid, when there's a mismatch between certificate domain and requested URL, or when self-signed certificates aren't properly configured.

The connection is rejected as untrusted, preventing your application from accessing APIs, downloading files, or communicating with secure services. This security feature protects against man-in-the-middle attacks, but legitimate connections can fail due to configuration issues. The error typically appears as a cURL error code with a descriptive message about SSL verification failure.

The Error You'll See

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

cURL error 77: Problem with the SSL CA cert (path? access rights?)

cURL error 35: SSL connect error

cURL error 51: SSL: certificate subject name 'example.com' does not match target host name 'api.example.com'

SSL certificate problem: self signed certificate

SSL certificate problem: unable to get local issuer certificate

SSL: certificate signature failure

SSL certificate problem: certificate has expired

cURL error 60: SSL certificate problem: unable to get local issuer certificate in /var/www/html/api.php on line 45

[08-Apr-2026 16:45:23 UTC] PHP Warning: file_get_contents(): SSL certificate problem: unable to get local issuer certificate in /var/www/app/services/HttpClient.php on line 89

SSL: no alternative certificate subject name matches target host name 'api.example.com'

error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed ```

Error codes explained: - 60: SSL certificate problem (most common) - 77: Problem with CA cert path or access rights - 51: Certificate host mismatch - 35: Generic SSL connection error

Why This Happens

  1. 1.Missing CA certificate bundle: PHP/cURL doesn't have access to a bundle of trusted CA certificates. Without this, it cannot verify that server certificates were issued by legitimate certificate authorities like DigiCert, Let's Encrypt, or GlobalSign.
  2. 2.Outdated CA bundle: Certificate authorities are periodically revoked or added. An outdated CA bundle won't include newer certificates or may fail verification for recently issued certificates.
  3. 3.Wrong CA bundle path: The php.ini curl.cainfo setting points to an incorrect or non-existent file path, or the path isn't configured at all.
  4. 4.Self-signed certificates: Development servers often use self-signed certificates that aren't in any CA bundle. By default, cURL rejects these as untrusted.
  5. 5.Certificate chain incomplete: Some servers don't send the full certificate chain, missing intermediate certificates. The client can't verify the chain from leaf to root CA.
  6. 6.Domain mismatch: The certificate is issued for one domain (e.g., example.com) but you're connecting to a different one (e.g., subdomain.example.com or api.example.com).
  7. 7.Expired certificate: The server's SSL certificate has passed its expiration date. cURL correctly rejects expired certificates.
  8. 8.Certificate revoked: The certificate has been revoked by the issuing CA, but your bundle doesn't have the means to check OCSP/CRL.
  9. 9.Server misconfiguration: The server presents the wrong certificate, has TLS version issues, or cipher suite problems that prevent successful negotiation.
  10. 10.Windows CA store issues: On Windows, PHP may not properly access the Windows certificate store, requiring manual CA bundle configuration.

Step 1: Check Current SSL Configuration

Diagnose your current SSL setup:

```bash # Check PHP cURL configuration php -i | grep -i "curl" php -r "echo 'cURL version: ' . curl_version()['version'] . '\n';"

# Check SSL settings in php.ini grep -i "curl.cainfo" /etc/php/*/fpm/php.ini grep -i "openssl.cafile" /etc/php/*/fpm/php.ini

# Check if CA bundle is configured php -r " \$curl = curl_init('https://www.google.com'); curl_setopt(\$curl, CURLOPT_RETURNTRANSFER, true); \$result = curl_exec(\$curl); if (curl_errno(\$curl)) { echo 'Error: ' . curl_error(\$curl) . '\n'; echo 'Error code: ' . curl_errno(\$curl) . '\n'; } curl_close(\$curl); "

# Check OpenSSL configuration php -r "print_r(openssl_get_cert_locations());"

# Check if cURL is compiled with SSL support curl-config --configure 2>/dev/null || echo "curl-config not found" curl --version | grep -i ssl

# Test direct cURL from command line curl -v https://www.google.com 2>&1 | head -30 curl -v https://api.github.com 2>&1 | grep -i "SSL|certificate"

# Check certificate bundle location for system cURL curl-config --ca 2>/dev/null || echo "Default CA bundle location unknown" ```

Test a real HTTPS connection:

```php <?php // ssl_test.php - Diagnose SSL connection issues function testSSLConnection($url) { echo "Testing: $url\n";

$ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_VERBOSE => true, ]);

// Capture verbose output $verbose = fopen('php://temp', 'w+'); curl_setopt($ch, CURLOPT_STDERR, $verbose);

$result = curl_exec($ch);

if (curl_errno($ch)) { echo "Error: " . curl_error($ch) . " (code: " . curl_errno($ch) . ")\n";

// Rewind and read verbose output rewind($verbose); $verboseLog = stream_get_contents($verbose); echo "Verbose log:\n$verboseLog\n"; } else { echo "Success! Response length: " . strlen($result) . " bytes\n"; }

// Get certificate info $certInfo = curl_getinfo($ch, CURLINFO_CERTINFO); if ($certInfo) { echo "Certificate info:\n"; print_r($certInfo); }

curl_close($ch);

return $result !== false; }

// Test multiple endpoints $tests = [ 'https://www.google.com', 'https://api.github.com', 'https://jsonplaceholder.typicode.com', ];

foreach ($tests as $url) { testSSLConnection($url); echo "\n---\n"; } ?> ```

Step 2: Download and Install CA Certificate Bundle

Install a proper CA certificate bundle:

```bash # Download Mozilla CA bundle (recommended) cd /etc/ssl/certs/ # or appropriate location

# Download from cURL official source wget https://curl.se/ca/cacert.pem # Or from Mozilla wget https://raw.githubusercontent.com/curl/curl/master/lib/mk-ca-bundle.pl

# Alternative: Use system CA bundle # Ubuntu/Debian ls -la /etc/ssl/certs/ca-certificates.crt

# CentOS/RHEL ls -la /etc/pki/tls/certs/ca-bundle.crt

# macOS ls -la /etc/ssl/cert.pem

# Windows - Download to appropriate location # Download cacert.pem to C:\php\cacert.pem or similar

# Verify downloaded bundle openssl x509 -in cacert.pem -text -noout | head -20 grep -c "BEGIN CERTIFICATE" cacert.pem # Should show many certificates

# Make readable chmod 644 cacert.pem ls -la cacert.pem

# Update system CA certificates (Ubuntu/Debian) sudo update-ca-certificates

# CentOS/RHEL sudo update-ca-trust

# Test with downloaded bundle curl --cacert /etc/ssl/certs/cacert.pem https://www.google.com ```

Alternative sources for CA bundles:

```bash # cURL official (recommended) curl -o /etc/ssl/certs/cacert.pem https://curl.se/ca/cacert.pem

# Mozilla via mk-ca-bundle script curl -o mk-ca-bundle.pl https://raw.githubusercontent.com/curl/curl/master/lib/mk-ca-bundle.pl perl mk-ca-bundle.pl -b cacert.pem -u

# Ubuntu/Debian package sudo apt-get install ca-certificates sudo update-ca-certificates --fresh

# CentOS/RHEL sudo yum install ca-certificates sudo update-ca-trust extract

# Using certifi (Python package) pip3 install certifi python3 -c "import certifi; print(certifi.where())" ```

Step 3: Configure php.ini for CA Bundle

Set the CA bundle path in PHP configuration:

```bash # Find php.ini location php --ini

# Edit appropriate php.ini sudo nano /etc/php/8.2/fpm/php.ini

# Add or modify these lines: curl.cainfo = "/etc/ssl/certs/cacert.pem" openssl.cafile = "/etc/ssl/certs/cacert.pem"

# For Windows: # curl.cainfo = "C:\php\cacert.pem" # openssl.cafile = "C:\php\cacert.pem"

# Verify settings php -i | grep curl.cainfo php -i | grep openssl.cafile

# Restart PHP-FPM sudo systemctl restart php8.2-fpm

# For Apache mod_php sudo systemctl restart apache2

# For Nginx sudo systemctl restart nginx php8.2-fpm ```

Configure via PHP code at runtime:

```php <?php // Set CA bundle at runtime (before any cURL requests) ini_set('curl.cainfo', '/etc/ssl/certs/cacert.pem'); ini_set('openssl.cafile', '/etc/ssl/certs/cacert.pem');

// Verify configuration echo "curl.cainfo: " . ini_get('curl.cainfo') . "\n"; echo "openssl.cafile: " . ini_get('openssl.cafile') . "\n";

// Test after setting $ch = curl_init('https://www.google.com'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch);

if (curl_errno($ch)) { echo "Error: " . curl_error($ch) . "\n"; } else { echo "Success!\n"; } curl_close($ch);

// Bootstrap file configuration // Add to your application bootstrap (config/bootstrap.php) function configureSSL() { $caBundle = getenv('CA_BUNDLE_PATH') ?: '/etc/ssl/certs/cacert.pem';

if (!file_exists($caBundle)) { throw new RuntimeException("CA bundle not found at: $caBundle"); }

ini_set('curl.cainfo', $caBundle); ini_set('openssl.cafile', $caBundle);

// Log configuration error_log("SSL configured with CA bundle: $caBundle"); }

// Call early in application startup configureSSL(); ?> ```

Step 4: Configure cURL Options for SSL

Set cURL SSL options explicitly in your requests:

```php <?php class SecureHttpClient { private $caBundlePath; private $defaultOptions = [];

public function __construct($caBundlePath = null) { // Find CA bundle $this->caBundlePath = $caBundlePath ?: $this->findCaBundle();

$this->defaultOptions = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => $this->caBundlePath, ]; }

private function findCaBundle() { $candidates = [ '/etc/ssl/certs/cacert.pem', '/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/usr/local/etc/ssl/cert.pem', '/usr/local/share/cert.pem', '/usr/share/ssl/cert.pem', getenv('CA_BUNDLE_PATH'), ];

foreach ($candidates as $path) { if ($path && file_exists($path)) { return $path; } }

// Fallback: download throw new RuntimeException('CA certificate bundle not found'); }

public function get($url, $options = []) { return $this->request('GET', $url, $options); }

public function post($url, $data, $options = []) { $options[CURLOPT_POST] = true; $options[CURLOPT_POSTFIELDS] = $data; return $this->request('POST', $url, $options); }

private function request($method, $url, $options = []) { $ch = curl_init($url);

// Merge default with custom options $allOptions = $this->defaultOptions + $options; curl_setopt_array($ch, $allOptions);

$result = curl_exec($ch);

if (curl_errno($ch)) { $error = curl_error($ch); $errno = curl_errno($ch); curl_close($ch);

throw new CurlException("cURL error ($errno): $error", $errno); }

$info = curl_getinfo($ch); curl_close($ch);

return [ 'body' => $result, 'status' => $info['http_code'], 'info' => $info, ]; }

// For development/testing with self-signed certs public function requestInsecure($method, $url, $options = []) { // WARNING: Only use in development! $options[CURLOPT_SSL_VERIFYPEER] = false; $options[CURLOPT_SSL_VERIFYHOST] = 0;

return $this->request($method, $url, $options); } }

// Usage $client = new SecureHttpClient();

try { $response = $client->get('https://api.example.com/data'); echo $response['body']; } catch (CurlException $e) { echo "Request failed: " . $e->getMessage(); }

// Simple function approach function secureCurlRequest($url, $options = []) { $ch = curl_init($url);

// Essential SSL options $sslOptions = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, // Verify peer's certificate CURLOPT_SSL_VERIFYHOST => 2, // Verify host matches certificate CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', ];

curl_setopt_array($ch, $sslOptions + $options);

$result = curl_exec($ch);

if (curl_errno($ch)) { $error = [ 'code' => curl_errno($ch), 'message' => curl_error($ch), ]; curl_close($ch); return ['error' => $error]; }

curl_close($ch); return ['success' => true, 'data' => $result]; } ?> ```

Step 5: Handle Self-Signed Certificates

Configure for self-signed or internal certificates:

```php <?php // For development environments with self-signed certificates // WARNING: Never use this in production!

// Option 1: Disable verification (dangerous!) function insecureRequest($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, // Don't verify peer cert CURLOPT_SSL_VERIFYHOST => 0, // Don't verify host name ]);

$result = curl_exec($ch); curl_close($ch);

return $result; }

// Option 2: Specify custom CA file (for internal certificates) function requestWithCustomCA($url, $caFile) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => $caFile, // Your self-signed cert or internal CA ]);

$result = curl_exec($ch);

if (curl_errno($ch)) { // If still fails, check certificate format throw new Exception(curl_error($ch)); }

curl_close($ch); return $result; }

// Option 3: Extract server certificate and trust it function trustServerCertificate($url) { // First, get the certificate without verification $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_CERTINFO => true, ]);

$result = curl_exec($ch);

// Get certificate info $certInfo = curl_getinfo($ch, CURLINFO_CERTINFO); curl_close($ch);

// Now you can save/use this certificate info return $certInfo; }

// Option 4: Environment-aware configuration class EnvironmentAwareHttpClient { private $sslVerify;

public function __construct() { // Only disable SSL verification in development $this->sslVerify = getenv('APP_ENV') !== 'development'; }

public function request($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => $this->sslVerify, CURLOPT_SSL_VERIFYHOST => $this->sslVerify ? 2 : 0, ]);

// In production, always use CA bundle if ($this->sslVerify) { curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/cacert.pem'); }

$result = curl_exec($ch);

if (curl_errno($ch)) { error_log('cURL error: ' . curl_error($ch)); }

curl_close($ch); return $result; } }

// Create self-signed CA bundle function createLocalCaBundle($certPath) { // Combine system CA bundle with local certificates $systemBundle = '/etc/ssl/certs/ca-certificates.crt'; $localBundle = '/tmp/local_cacert.pem';

if (file_exists($systemBundle)) { copy($systemBundle, $localBundle); }

// Append your local/self-signed certificate if (file_exists($certPath)) { file_put_contents($localBundle, "\n" . file_get_contents($certPath), FILE_APPEND); }

return $localBundle; }

// Use local bundle $localBundle = createLocalCaBundle('/path/to/internal-ca.crt'); $client = new SecureHttpClient($localBundle); ?> ```

Step 6: Fix Certificate Domain Mismatch

Handle certificate hostname issues:

```php <?php // cURL error 51: certificate subject name does not match target host

// Option 1: Use correct hostname // If certificate is for example.com, use example.com not api.example.com

// Option 2: Use Server Name Indication (SNI) function requestWithSNI($url, $hostname) { $ch = curl_init($url);

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', // Set SNI hostname CURLOPT_SSL_OPTIONS => CURLSSLOPT_NO_REVOKE, // If needed ]);

$result = curl_exec($ch); curl_close($ch);

return $result; }

// Option 3: Disable host verification (not recommended) function requestIgnoreHostname($url) { $ch = curl_init($url);

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, // Still verify cert validity CURLOPT_SSL_VERIFYHOST => 0, // Don't check hostname match CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', ]);

$result = curl_exec($ch); curl_close($ch);

return $result; }

// Option 4: Check certificate before connecting function checkCertificateHostname($url, $expectedHostname) { // Parse URL $parsed = parse_url($url); $host = $parsed['host'];

// Get certificate $context = stream_context_create([ 'ssl' => [ 'capture_peer_cert' => true, 'verify_peer' => false, // Just inspecting 'verify_peer_name' => false, ] ]);

$client = stream_socket_client( "ssl://{$host}:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context );

$cert = stream_context_get_params($client)['options']['ssl']['peer_certificate'];

// Parse certificate $certInfo = openssl_x509_parse($cert);

// Check common name and SAN $cn = $certInfo['subject']['CN'] ?? ''; $san = $certInfo['extensions']['subjectAltName'] ?? '';

$validHosts = [$cn];

// Parse SAN if ($san) { preg_match_all('/DNS:([^,\s]+)/', $san, $matches); $validHosts = array_merge($validHosts, $matches[1]); }

fclose($client);

if (in_array($expectedHostname, $validHosts)) { return ['valid' => true, 'hosts' => $validHosts]; }

return ['valid' => false, 'hosts' => $validHosts, 'expected' => $expectedHostname]; }

// Usage $check = checkCertificateHostname('https://api.example.com', 'api.example.com'); if (!$check['valid']) { echo "Certificate is for: " . implode(', ', $check['hosts']) . "\n"; echo "You requested: " . $check['expected'] . "\n"; } ?> ```

Step 7: Handle Certificate Chain Issues

Fix incomplete certificate chain problems:

```php <?php // Some servers don't send full certificate chain

// Option 1: Request full chain info function getCertificateChain($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', CURLOPT_CERTINFO => true, ]);

curl_exec($ch);

$certInfo = curl_getinfo($ch, CURLINFO_CERTINFO); curl_close($ch);

return $certInfo; }

// Option 2: Manually specify intermediate certificates function requestWithIntermediateCert($url, $intermediateCert) { // Create bundle with intermediate cert $bundle = '/tmp/custom_bundle.pem';

// Start with system CA bundle copy('/etc/ssl/certs/cacert.pem', $bundle);

// Add intermediate certificate file_put_contents($bundle, file_get_contents($intermediateCert), FILE_APPEND);

$ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => $bundle, ]);

$result = curl_exec($ch); curl_close($ch);

// Clean up unlink($bundle);

return $result; }

// Option 3: Download and verify certificates manually function downloadAndVerifyCert($hostname, $port = 443) { $context = stream_context_create([ 'ssl' => [ 'capture_peer_cert_chain' => true, 'verify_peer' => false, 'verify_peer_name' => false, ] ]);

$client = stream_socket_client( "ssl://{$hostname}:{$port}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context );

$params = stream_context_get_params($client); $chain = $params['options']['ssl']['peer_certificate_chain'] ?? [];

$certificates = [];

foreach ($chain as $cert) { $certData = openssl_x509_read($cert); $certInfo = openssl_x509_parse($certData);

$certificates[] = [ 'subject' => $certInfo['subject'], 'issuer' => $certInfo['issuer'], 'valid_from' => date('Y-m-d', $certInfo['validFrom_time_t']), 'valid_to' => date('Y-m-d', $certInfo['validTo_time_t']), 'is_ca' => isset($certInfo['extensions']['basicConstraints']) && strpos($certInfo['extensions']['basicConstraints'], 'CA:TRUE') !== false, ];

// Export certificate openssl_x509_export($certData, $certPem); file_put_contents("/tmp/cert_{$certInfo['subject']['CN']}.pem", $certPem); }

fclose($client);

return $certificates; }

// Verify certificate chain function verifyCertificateChain($hostname) { $certs = downloadAndVerifyCert($hostname);

echo "Certificate chain for $hostname:\n";

foreach ($certs as $cert) { echo "- Subject: {$cert['subject']['CN']}\n"; echo " Issuer: {$cert['issuer']['CN']}\n"; echo " Valid: {$cert['valid_from']} to {$cert['valid_to']}\n"; echo " Is CA: " . ($cert['is_ca'] ? 'Yes' : 'No') . "\n";

// Check expiration $expires = strtotime($cert['valid_to']); if ($expires < time()) { echo " WARNING: Certificate expired!\n"; } elseif ($expires < time() + 30 * 24 * 3600) { echo " WARNING: Certificate expires in less than 30 days!\n"; } } } ?> ```

Step 8: Use TLS Version and Cipher Configuration

Configure specific TLS versions and cipher suites:

```php <?php // Some servers require specific TLS versions

function requestWithTlsVersion($url, $tlsVersion = '1.2') { $ch = curl_init($url);

$tlsConstant = [ '1.0' => CURL_SSLVERSION_TLSv1_0, '1.1' => CURL_SSLVERSION_TLSv1_1, '1.2' => CURL_SSLVERSION_TLSv1_2, '1.3' => CURL_SSLVERSION_TLSv1_3, ];

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', CURLOPT_SSLVERSION => $tlsConstant[$tlsVersion] ?? CURL_SSLVERSION_TLSv1_2, ]);

$result = curl_exec($ch);

if (curl_errno($ch)) { echo "Error: " . curl_error($ch) . "\n"; }

curl_close($ch); return $result; }

// Specify cipher list (rarely needed) function requestWithCipher($url, $cipherList) { $ch = curl_init($url);

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', CURLOPT_SSL_CIPHER_LIST => $cipherList, ]);

$result = curl_exec($ch); curl_close($ch);

return $result; }

// Test different TLS versions function testTlsVersions($url) { $versions = ['1.0', '1.1', '1.2', '1.3'];

echo "Testing TLS versions for: $url\n";

foreach ($versions as $version) { $ch = curl_init($url);

$tlsConstant = [ '1.0' => CURL_SSLVERSION_TLSv1_0, '1.1' => CURL_SSLVERSION_TLSv1_1, '1.2' => CURL_SSLVERSION_TLSv1_2, '1.3' => CURL_SSLVERSION_TLSv1_3, ];

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => '/etc/ssl/certs/cacert.pem', CURLOPT_SSLVERSION => $tlsConstant[$version], ]);

$result = curl_exec($ch); $error = curl_errno($ch);

echo "TLS $version: " . ($error ? "Failed (" . curl_error($ch) . ")" : "Success") . "\n";

curl_close($ch); } }

// Enable TLS 1.3 if available if (defined('CURL_SSLVERSION_TLSv1_3')) { requestWithTlsVersion('https://example.com', '1.3'); } ?> ```

Step 9: Debug SSL Connection Issues

Create comprehensive debugging tools:

```php <?php class SSLDebugger { public function diagnose($url) { $report = [ 'url' => $url, 'parsed_url' => parse_url($url), 'php_version' => PHP_VERSION, 'curl_version' => curl_version()['version'], 'ssl_library' => curl_version()['ssl_version'], ];

// Check CA bundle configuration $report['ca_config'] = [ 'curl.cainfo' => ini_get('curl.cainfo'), 'openssl.cafile' => ini_get('openssl.cafile'), 'openssl.capath' => ini_get('openssl.capath'), ];

// OpenSSL cert locations $report['openssl_locations'] = openssl_get_cert_locations();

// Test connection $report['connection_test'] = $this->testConnection($url);

// Certificate details $report['certificate'] = $this->getCertificateDetails($url);

return $report; }

private function testConnection($url) { $ch = curl_init($url);

// Capture verbose output $verbose = fopen('php://temp', 'w+');

curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_VERBOSE => true, CURLOPT_STDERR => $verbose, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => ini_get('curl.cainfo') ?: '/etc/ssl/certs/cacert.pem', CURLOPT_CERTINFO => true, ]);

$result = curl_exec($ch);

$testResult = [ 'success' => $result !== false, 'error_code' => curl_errno($ch), 'error_message' => curl_error($ch), 'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), 'ssl_verify_result' => curl_getinfo($ch, CURLINFO_SSL_VERIFYRESULT), ];

// Get verbose log rewind($verbose); $testResult['verbose_log'] = stream_get_contents($verbose);

// Get certificate info $testResult['cert_info'] = curl_getinfo($ch, CURLINFO_CERTINFO);

curl_close($ch);

return $testResult; }

private function getCertificateDetails($url) { $parsed = parse_url($url); $host = $parsed['host']; $port = $parsed['port'] ?? 443;

$context = stream_context_create([ 'ssl' => [ 'capture_peer_cert' => true, 'capture_peer_cert_chain' => true, 'verify_peer' => false, 'verify_peer_name' => false, ] ]);

$client = @stream_socket_client( "ssl://{$host}:{$port}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context );

if (!$client) { return ['error' => "Could not connect: $errstr"]; }

$params = stream_context_get_params($client); $cert = $params['options']['ssl']['peer_certificate'];

$certInfo = openssl_x509_parse($cert);

$details = [ 'subject' => $certInfo['subject'], 'issuer' => $certInfo['issuer'], 'valid_from' => date('Y-m-d H:i:s', $certInfo['validFrom_time_t']), 'valid_to' => date('Y-m-d H:i:s', $certInfo['validTo_time_t']), 'serial_number' => $certInfo['serialNumber'], 'signature_type' => $certInfo['signatureTypeSN'], ];

// Check expiration $expires = $certInfo['validTo_time_t']; $details['expired'] = $expires < time(); $details['expires_in_days'] = floor(($expires - time()) / 86400);

// Get SAN if (isset($certInfo['extensions']['subjectAltName'])) { preg_match_all('/DNS:([^,\s]+)/', $certInfo['extensions']['subjectAltName'], $matches); $details['valid_hostnames'] = $matches[1]; }

// Check if hostname matches $details['hostname_matches'] = in_array($host, $details['valid_hostnames'] ?? [$certInfo['subject']['CN']]);

fclose($client);

return $details; }

public function printReport($report) { echo "=== SSL Connection Diagnosis ===\n\n";

echo "URL: {$report['url']}\n"; echo "Host: {$report['parsed_url']['host']}\n"; echo "PHP Version: {$report['php_version']}\n"; echo "cURL Version: {$report['curl_version']}\n"; echo "SSL Library: {$report['ssl_library']}\n\n";

echo "CA Configuration:\n"; foreach ($report['ca_config'] as $key => $value) { echo " $key: " . ($value ?: 'Not configured') . "\n"; } echo "\n";

echo "Connection Test:\n"; $test = $report['connection_test']; echo " Success: " . ($test['success'] ? 'Yes' : 'No') . "\n";

if (!$test['success']) { echo " Error Code: {$test['error_code']}\n"; echo " Error Message: {$test['error_message']}\n"; }

echo " HTTP Code: {$test['http_code']}\n"; echo " SSL Verify Result: {$test['ssl_verify_result']}\n\n";

if (isset($report['certificate']['error'])) { echo "Certificate: {$report['certificate']['error']}\n"; } else { $cert = $report['certificate']; echo "Certificate Details:\n"; echo " Subject: {$cert['subject']['CN']}\n"; echo " Issuer: {$cert['issuer']['CN']}\n"; echo " Valid From: {$cert['valid_from']}\n"; echo " Valid To: {$cert['valid_to']}\n"; echo " Expired: " . ($cert['expired'] ? 'YES!' : 'No') . "\n"; echo " Expires in: {$cert['expires_in_days']} days\n"; echo " Hostname Matches: " . ($cert['hostname_matches'] ? 'Yes' : 'NO!') . "\n";

if ($cert['valid_hostnames']) { echo " Valid Hostnames: " . implode(', ', $cert['valid_hostnames']) . "\n"; } } } }

// Usage $debugger = new SSLDebugger(); $report = $debugger->diagnose('https://api.example.com'); $debugger->printReport($report);

// Quick diagnostic function quickSSLDiagnosis($url) { $debugger = new SSLDebugger(); $report = $debugger->diagnose($url);

// Check key issues $issues = [];

if (!$report['connection_test']['success']) { $issues[] = "Connection failed: " . $report['connection_test']['error_message']; }

if (!$report['ca_config']['curl.cainfo']) { $issues[] = "CA bundle not configured in php.ini"; }

if ($report['certificate']['expired']) { $issues[] = "Certificate expired"; }

if (!$report['certificate']['hostname_matches']) { $issues[] = "Hostname doesn't match certificate"; }

return $issues; }

$issues = quickSSLDiagnosis('https://api.example.com'); if ($issues) { echo "Issues found:\n"; foreach ($issues as $issue) { echo "- $issue\n"; } } ?> ```

Step 10: Automate CA Bundle Updates

Keep CA bundle updated automatically:

```php <?php // scripts/update_ca_bundle.php

class CABundleUpdater { private $bundlePath; private $sourceUrl = 'https://curl.se/ca/cacert.pem';

public function __construct($bundlePath = '/etc/ssl/certs/cacert.pem') { $this->bundlePath = $bundlePath; }

public function update() { echo "Updating CA certificate bundle...\n";

// Backup existing bundle if (file_exists($this->bundlePath)) { $backup = $this->bundlePath . '.backup.' . date('Ymd'); copy($this->bundlePath, $backup); echo "Backed up existing bundle to: $backup\n"; }

// Download new bundle $tempFile = $this->bundlePath . '.tmp';

$result = $this->download($this->sourceUrl, $tempFile);

if (!$result['success']) { throw new RuntimeException("Failed to download CA bundle: " . $result['error']); }

// Verify downloaded bundle $count = $this->countCertificates($tempFile);

if ($count < 100) { throw new RuntimeException("Downloaded bundle has too few certificates ($count)"); }

echo "Downloaded bundle contains $count certificates\n";

// Replace existing bundle rename($tempFile, $this->bundlePath); chmod($this->bundlePath, 0644);

echo "CA bundle updated successfully!\n";

// Test with new bundle $testResult = $this->testBundle($this->bundlePath);

if (!$testResult['success']) { throw new RuntimeException("Test failed with new bundle: " . $testResult['error']); }

echo "Test successful!\n";

return true; }

private function download($url, $dest) { // Use insecure download (for initial setup) // Then verify the downloaded file

$ch = curl_init($url); $fp = fopen($dest, 'w');

curl_setopt_array($ch, [ CURLOPT_FILE => $fp, CURLOPT_TIMEOUT => 60, CURLOPT_SSL_VERIFYPEER => false, // Initial download may fail without bundle ]);

$result = curl_exec($ch); $error = curl_errno($ch) ? curl_error($ch) : null;

curl_close($ch); fclose($fp);

return [ 'success' => $result && !$error, 'error' => $error ]; }

private function countCertificates($file) { $content = file_get_contents($file); return substr_count($content, 'BEGIN CERTIFICATE'); }

private function testBundle($bundlePath) { ini_set('curl.cainfo', $bundlePath);

$ch = curl_init('https://www.google.com'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => $bundlePath, ]);

$result = curl_exec($ch); $error = curl_errno($ch) ? curl_error($ch) : null;

curl_close($ch);

return [ 'success' => $result && !$error, 'error' => $error ]; }

public function checkExpiry($bundlePath) { // Check certificates in bundle for upcoming expiry $content = file_get_contents($bundlePath);

$certs = []; preg_match_all('/-----BEGIN CERTIFICATE-----(.+?)-----END CERTIFICATE-----/', $content, $matches);

foreach ($matches[1] as $certData) { $cert = "-----BEGIN CERTIFICATE-----" . $certData . "-----END CERTIFICATE-----"; $parsed = openssl_x509_parse($cert);

if ($parsed) { $expires = $parsed['validTo_time_t']; $daysUntilExpiry = floor(($expires - time()) / 86400);

if ($daysUntilExpiry < 30) { $certs[] = [ 'subject' => $parsed['subject']['CN'], 'expires_in_days' => $daysUntilExpiry, 'expired' => $daysUntilExpiry < 0, ]; } } }

return $certs; } }

// Cron job script // Run weekly to keep bundle updated // /etc/cron.d/update-ca-bundle: // 0 3 * * 0 root php /path/to/scripts/update_ca_bundle.php

if (php_sapi_name() === 'cli') { $updater = new CABundleUpdater();

try { $updater->update();

// Check for expiring certificates $expiring = $updater->checkExpiry('/etc/ssl/certs/cacert.pem');

if ($expiring) { echo "\nCertificates expiring soon:\n"; foreach ($expiring as $cert) { echo " {$cert['subject']}: expires in {$cert['expires_in_days']} days\n"; } }

} catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; exit(1); } } ?> ```

Checklist

StepActionVerified
1Checked current SSL configuration
2Downloaded and installed CA certificate bundle
3Configured php.ini curl.cainfo setting
4Set cURL SSL options in code
5Configured handling for self-signed certificates
6Fixed certificate domain mismatch issues
7Addressed incomplete certificate chains
8Configured TLS version if needed
9Created SSL debugging tools
10Set up automated CA bundle updates
11Tested HTTPS connections successfully
12Verified in production environment

Verify the Fix

  1. 1.Test basic HTTPS connection:
  2. 2.```php
  3. 3.<?php
  4. 4.$ch = curl_init('https://www.google.com');
  5. 5.curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  6. 6.$result = curl_exec($ch);

if (curl_errno($ch)) { echo "Failed: " . curl_error($ch) . "\n"; } else { echo "Success! Response length: " . strlen($result) . "\n"; } curl_close($ch); ?> ```

  1. 1.Verify CA bundle configuration:
  2. 2.```bash
  3. 3.php -i | grep curl.cainfo
  4. 4.php -i | grep openssl.cafile

# Test with explicit bundle curl --cacert /etc/ssl/certs/cacert.pem https://www.google.com ```

  1. 1.Test multiple endpoints:
  2. 2.```bash
  3. 3.php -r "
  4. 4.\$urls = ['https://google.com', 'https://github.com', 'https://api.twitter.com'];
  5. 5.foreach (\$urls as \$url) {
  6. 6.\$ch = curl_init(\$url);
  7. 7.curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
  8. 8.curl_exec(\$ch);
  9. 9.echo \$url . ': ' . (curl_errno(\$ch) ? curl_error(\$ch) : 'OK') . '\n';
  10. 10.curl_close(\$ch);
  11. 11.}
  12. 12."
  13. 13.`
  14. 14.Run SSL debugger:
  15. 15.```php
  16. 16.<?php
  17. 17.require 'SSLDebugger.php';
  18. 18.$debugger = new SSLDebugger();
  19. 19.$report = $debugger->diagnose('https://api.example.com');
  20. 20.$debugger->printReport($report);
  21. 21.?>
  22. 22.`
  23. 23.Check certificate details:
  24. 24.```bash
  25. 25.# View certificate chain
  26. 26.openssl s_client -connect google.com:443 -showcerts

# Check specific certificate openssl x509 -in /etc/ssl/certs/cacert.pem -text -noout | head -20 ```

  1. 1.Monitor SSL errors in production:
  2. 2.```bash
  3. 3.tail -f /var/log/php-fpm/error.log | grep -i "ssl|certificate|curl"

grep -c "SSL certificate problem" /var/log/php-fpm/error.log ```

  • [Fix PHP Class Not Found Autoload](/articles/fix-php-class-not-found-autoload)
  • [Fix Guzzle HTTP Client Connection Failed](/articles/fix-guzzle-http-client-connection-failed)
  • [Fix API Response Parsing Error](/articles/fix-api-response-parsing-error)
  • [Fix OpenSSL Encryption Decryption Error](/articles/fix-openssl-encryption-decryption-error)
  • [Fix Nginx SSL Certificate Configuration](/articles/fix-nginx-ssl-certificate-configuration)
  • [Fix Apache SSL HTTPS Not Working](/articles/fix-apache-ssl-https-not-working)
  • [Fix Laravel API HTTPS Request Failed](/articles/fix-laravel-api-https-request-failed)
  • [Fix WordPress REST API SSL Error](/articles/fix-wordpress-rest-api-ssl-error)
  • [Fix PayPal API SSL Certificate Error](/articles/fix-paypal-api-ssl-certificate-error)
  • [Fix Let's Encrypt Certificate Renewal Failed](/articles/fix-lets-encrypt-certificate-renewal-failed)