TL;DR โ Quick Fix
PHP can't verify the SSL certificate of the remote server when you call file_get_contents() on an HTTPS URL. Point PHP at a valid CA bundle in php.ini and you're done:
; In php.ini
openssl.cafile=/etc/ssl/certs/ca-certificates.crt
Restart your web server after saving. Need something working right now in a local script? Skip to the stream context approach โ but avoid disabling SSL verification in production. Seriously.
The Error
Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
Warning: file_get_contents(): Failed to enable crypto
Warning: file_get_contents(https://example.com/api): failed to open stream: operation failed
PHP throws this when file_get_contents() tries to fetch an HTTPS URL but OpenSSL can't match the remote certificate against any trusted Certificate Authority (CA). A few situations reliably trigger it:
- Fresh PHP install with no CA bundle configured in
php.ini - PHP upgraded but
openssl.cafilestill points to a stale or deleted bundle - The target server uses a self-signed or expired certificate
- macOS, XAMPP, or WAMP โ these ship without system CA roots baked into OpenSSL
Fix 1 โ Point php.ini at the CA Bundle (Recommended)
Start by confirming the CA bundle actually exists on your system:
# Debian / Ubuntu
ls /etc/ssl/certs/ca-certificates.crt
# CentOS / RHEL / Fedora
ls /etc/pki/tls/certs/ca-bundle.crt
# macOS (after `brew install openssl`)
ls /usr/local/etc/openssl/cert.pem
Find your active php.ini with php --ini, then add these two lines under the [openssl] section:
[openssl]
openssl.cafile=/etc/ssl/certs/ca-certificates.crt
curl.cainfo=/etc/ssl/certs/ca-certificates.crt
Both lines matter. The first covers file_get_contents(); the second covers the curl extension, which you'll likely use too. Restart the server:
# Apache
sudo systemctl restart apache2 # or httpd
# Nginx + PHP-FPM
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx
Fix 2 โ Switch to curl Instead of file_get_contents
Honestly, curl is the better tool for this job anyway. It gives you explicit control over timeouts, redirects, and SSL โ plus readable error messages when something goes wrong:
<?php
function https_get(string $url): string|false {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
error_log('curl error: ' . curl_error($ch));
}
curl_close($ch);
return $response;
}
$data = https_get('https://example.com/api/data');
Notice CURLOPT_SSL_VERIFYHOST => 2 โ that's the correct value (not true, not 1). Value 2 tells curl to check that the certificate's Common Name actually matches the hostname you're connecting to.
Fix 3 โ Download a Fresh CA Bundle (XAMPP / WAMP / macOS)
Windows and macOS dev stacks often ship without any system CA roots. The fix is a one-time download of the Mozilla CA bundle, maintained by the curl project:
# Download cacert.pem
curl -o /path/to/cacert.pem https://curl.se/ca/cacert.pem
Then wire it up in php.ini:
openssl.cafile=C:\xampp\php\extras\ssl\cacert.pem
curl.cainfo=C:\xampp\php\extras\ssl\cacert.pem
Restart Apache from the XAMPP control panel. The bundle is ~200 KB and covers all major public CAs.
Fix 4 โ Pass a Stream Context (Per-Request CA Path)
No access to php.ini? Set the CA bundle directly in the call:
<?php
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
],
]);
$result = file_get_contents('https://example.com/api', false, $context);
This is handy for shared hosting environments where you can't edit php.ini globally. Keep verification enabled โ just point it at the right bundle.
Last Resort โ Disable SSL Verification (Dev Only)
Use this only on your local machine. It strips all certificate protection and opens you up to man-in-the-middle attacks. Never deploy this to production:
<?php
// โ ๏ธ DEVELOPMENT ONLY โ exposes you to MITM attacks
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
]);
$result = file_get_contents('https://example.com/api', false, $context);
Special Case โ Self-Signed Certificate
Internal services often use self-signed certs. Don't disable verification โ just point PHP at that specific cert instead of the system CA bundle:
<?php
$context = stream_context_create([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/path/to/self-signed-cert.pem',
],
]);
$result = file_get_contents('https://internal-service.local/api', false, $context);
Export the cert from your browser or ask whoever runs the internal service for the .pem file.
Verify the Fix
Quick sanity check from the terminal:
# Test via CLI
php -r "echo file_get_contents('https://www.google.com') ? 'OK' : 'FAILED';"
If that still fails, check what CA file PHP is actually reading:
php -r "print_r(openssl_get_cert_locations());"
Look at default_cert_file. Blank or pointing to a missing path? That's your problem. Set openssl.cafile in php.ini to a path that exists.
After restarting the server, confirm PHP loaded the updated settings:
php -i | grep -E 'cafile|cainfo'
Update CA Certificates on the OS
Sometimes the bundle itself is outdated โ especially on servers that haven't been touched in a while. Reinstall it from the OS package manager:
# Debian / Ubuntu
sudo apt-get install --reinstall ca-certificates
sudo update-ca-certificates
# CentOS / RHEL
sudo yum reinstall ca-certificates
sudo update-ca-trust

