TL;DR — Sửa Nhanh
PHP không thể xác minh chứng chỉ SSL của máy chủ từ xa khi bạn gọi file_get_contents() trên URL HTTPS. Hãy trỏ PHP đến một CA bundle hợp lệ trong php.ini là xong:
; Trong php.ini
openssl.cafile=/etc/ssl/certs/ca-certificates.crt
Khởi động lại web server sau khi lưu. Cần giải pháp chạy được ngay bây giờ trong script local? Xem cách dùng stream context — nhưng tuyệt đối tránh tắt xác thực SSL trên môi trường production. Nghiêm túc đấy.
Lỗi Gặp Phải
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 báo lỗi này khi file_get_contents() cố tải một URL HTTPS nhưng OpenSSL không thể khớp chứng chỉ của máy chủ với bất kỳ Certificate Authority (CA) đáng tin cậy nào. Một số tình huống thường gây ra lỗi này:
- Cài PHP mới nhưng chưa cấu hình CA bundle trong
php.ini - PHP đã nâng cấp nhưng
openssl.cafilevẫn trỏ đến bundle cũ hoặc đã bị xóa - Máy chủ đích dùng chứng chỉ tự ký hoặc đã hết hạn
- macOS, XAMPP hoặc WAMP — những môi trường này không đi kèm CA roots của hệ thống trong OpenSSL
Cách 1 — Trỏ php.ini Đến CA Bundle (Khuyến Nghị)
Đầu tiên, xác nhận rằng CA bundle thực sự tồn tại trên hệ thống của bạn:
# Debian / Ubuntu
ls /etc/ssl/certs/ca-certificates.crt
# CentOS / RHEL / Fedora
ls /etc/pki/tls/certs/ca-bundle.crt
# macOS (sau khi `brew install openssl`)
ls /usr/local/etc/openssl/cert.pem
Tìm php.ini đang hoạt động bằng lệnh php --ini, sau đó thêm hai dòng sau vào phần [openssl]:
[openssl]
openssl.cafile=/etc/ssl/certs/ca-certificates.crt
curl.cainfo=/etc/ssl/certs/ca-certificates.crt
Cả hai dòng đều cần thiết. Dòng đầu áp dụng cho file_get_contents(); dòng thứ hai áp dụng cho extension curl mà bạn cũng sẽ dùng đến. Khởi động lại server:
# Apache
sudo systemctl restart apache2 # hoặc httpd
# Nginx + PHP-FPM
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx
Cách 2 — Chuyển Sang curl Thay Vì file_get_contents
Thành thật mà nói, curl là công cụ phù hợp hơn cho việc này. Nó cho bạn kiểm soát rõ ràng timeout, redirect và SSL — cùng với thông báo lỗi dễ đọc khi có sự cố:
<?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');
Lưu ý CURLOPT_SSL_VERIFYHOST => 2 — đây là giá trị đúng (không phải true, không phải 1). Giá trị 2 yêu cầu curl kiểm tra xem Common Name trong chứng chỉ có thực sự khớp với hostname bạn đang kết nối hay không.
Cách 3 — Tải CA Bundle Mới (XAMPP / WAMP / macOS)
Các bộ công cụ dev trên Windows và macOS thường không đi kèm CA roots của hệ thống. Giải pháp là tải một lần Mozilla CA bundle do dự án curl duy trì:
# Tải cacert.pem
curl -o /path/to/cacert.pem https://curl.se/ca/cacert.pem
Sau đó cấu hình trong php.ini:
openssl.cafile=C:\xampp\php\extras\ssl\cacert.pem
curl.cainfo=C:\xampp\php\extras\ssl\cacert.pem
Khởi động lại Apache từ XAMPP control panel. Bundle có dung lượng khoảng 200 KB và hỗ trợ tất cả các CA công khai lớn.
Cách 4 — Dùng Stream Context (Cấu Hình CA Theo Từng Request)
Không có quyền truy cập php.ini? Hãy đặt CA bundle trực tiếp trong lời gọi hàm:
<?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);
Cách này hữu ích cho môi trường shared hosting khi bạn không thể chỉnh php.ini toàn cục. Hãy giữ nguyên việc xác thực — chỉ cần trỏ đúng đến bundle.
Biện Pháp Cuối Cùng — Tắt Xác Thực SSL (Chỉ Dùng Khi Dev)
Chỉ dùng cách này trên máy local của bạn. Nó loại bỏ toàn bộ bảo vệ chứng chỉ và khiến bạn dễ bị tấn công man-in-the-middle. Tuyệt đối không deploy lên production:
<?php
// ⚠️ CHỈ DÙNG KHI DEVELOPMENT — dễ bị tấn công MITM
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
]);
$result = file_get_contents('https://example.com/api', false, $context);
Trường Hợp Đặc Biệt — Chứng Chỉ Tự Ký
Các dịch vụ nội bộ thường dùng chứng chỉ tự ký. Đừng tắt xác thực — hãy trỏ PHP đến chính chứng chỉ đó thay vì CA bundle của hệ thống:
<?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);
Xuất chứng chỉ từ trình duyệt hoặc yêu cầu người quản lý dịch vụ nội bộ cung cấp file .pem.
Xác Minh Kết Quả
Kiểm tra nhanh từ terminal:
# Kiểm tra qua CLI
php -r "echo file_get_contents('https://www.google.com') ? 'OK' : 'FAILED';"
Nếu vẫn thất bại, kiểm tra PHP đang thực sự đọc CA file nào:
php -r "print_r(openssl_get_cert_locations());"
Xem giá trị default_cert_file. Trống hoặc trỏ đến đường dẫn không tồn tại? Đó là vấn đề của bạn. Hãy đặt openssl.cafile trong php.ini trỏ đến đường dẫn hợp lệ.
Sau khi khởi động lại server, xác nhận PHP đã tải cấu hình mới:
php -i | grep -E 'cafile|cainfo'
Cập Nhật CA Certificates Trên Hệ Điều Hành
Đôi khi bản thân bundle đã lỗi thời — đặc biệt trên các server lâu không được cập nhật. Cài lại từ trình quản lý gói của hệ điều hành:
# Debian / Ubuntu
sudo apt-get install --reinstall ca-certificates
sudo update-ca-certificates
# CentOS / RHEL
sudo yum reinstall ca-certificates
sudo update-ca-trust

