PHP에서 HTTPS 요청이나 SMTP 연결 시 SSL 인증서 오류는 자주 발생하는 문제입니다. 개발 환경과 운영 환경에서의 안전한 해결 방법을 알아보겠습니다.
문제: SSL certificate problem: unable to get local issuer certificate 오류
해결책: CA 인증서 번들 파일 설정
function secureCurlRequest($url, $data = null) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CAINFO => __DIR__ . '/cacert.pem', // CA 번들 파일 경로
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true
]);
if ($data) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
throw new Exception('cURL 오류: ' . curl_error($ch));
}
curl_close($ch);
return $response;
}
# CA 인증서 번들 다운로드
curl -o cacert.pem https://curl.se/ca/cacert.pem
문제: SMTP 연결 시 SSL 인증서 검증 실패
해결책: 단계별 SSL 옵션 설정
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
$mail = new PHPMailer(true);
// 운영 환경용 (권장)
$mail->SMTPOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
'cafile' => '/path/to/cacert.pem'
]
];
// 개발 환경용 (임시)
if ($_ENV['APP_ENV'] === 'development') {
$mail->SMTPOptions = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
]
];
}
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
문제: file_get_contents()나 스트림에서 SSL 오류
해결책: 스트림 컨텍스트로 SSL 옵션 설정
function secureFileGetContents($url, $options = []) {
$defaultOptions = [
'http' => [
'method' => 'GET',
'timeout' => 30,
'user_agent' => 'Mozilla/5.0 (compatible; PHP)'
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => __DIR__ . '/cacert.pem'
]
];
$context = stream_context_create(array_merge_recursive($defaultOptions, $options));
$result = file_get_contents($url, false, $context);
if ($result === false) {
throw new Exception("HTTPS 요청 실패: $url");
}
return $result;
}
// 사용 예시
try {
$data = secureFileGetContents('https://api.example.com/data');
echo $data;
} catch (Exception $e) {
echo "오류: " . $e->getMessage();
}
문제: 개발/테스트 환경의 자체 서명 인증서
해결책: 환경별 조건부 SSL 설정
class SSLHelper {
public static function createSecureContext($allowSelfSigned = false) {
$options = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => $allowSelfSigned
]
];
// CA 파일이 존재하면 설정
$caFile = __DIR__ . '/cacert.pem';
if (file_exists($caFile)) {
$options['ssl']['cafile'] = $caFile;
}
// 개발 환경에서는 검증 완화
if (defined('DEVELOPMENT') && DEVELOPMENT) {
$options['ssl']['verify_peer'] = false;
$options['ssl']['verify_peer_name'] = false;
}
return stream_context_create($options);
}
public static function curlWithSSL($url, $allowSelfSigned = false) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => !$allowSelfSigned,
CURLOPT_SSL_VERIFYHOST => $allowSelfSigned ? 0 : 2
]);
if (!$allowSelfSigned && file_exists(__DIR__ . '/cacert.pem')) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem');
}
return $ch;
}
}
문제: 애플리케이션 전체의 SSL 설정 필요
해결책: php.ini 또는 런타임 설정 조정
// 런타임에 OpenSSL 설정
ini_set('openssl.cafile', '/path/to/cacert.pem');
ini_set('curl.cainfo', '/path/to/cacert.pem');
// 기본 스트림 컨텍스트 설정
$context = stream_context_get_default([
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/path/to/cacert.pem'
]
]);
// 설정 확인 함수
function checkSSLConfig() {
$info = [
'OpenSSL Version' => OPENSSL_VERSION_TEXT,
'CA File' => ini_get('openssl.cafile'),
'cURL CA Info' => ini_get('curl.cainfo'),
'SSL Support' => extension_loaded('openssl') ? 'Yes' : 'No'
];
return $info;
}
print_r(checkSSLConfig());
SSL 인증서 오류는 보안과 직결되므로 운영 환경에서는 반드시 올바른 인증서 검증을 유지해야 합니다. 개발 환경에서만 임시로 검증을 비활성화하고, CA 번들 파일을 최신 상태로 유지하는 것이 중요합니다.