외부 API와 통신하거나 HTTPS 웹사이트에서 데이터를 수집할 때 갑자기 cURL error 60: SSL certificate problem: unable to get local issuer certificate 에러가 발생하며 요청이 중단되는 현상을 경험해봤을 것이다.
다만 정확한 원인을 모른 채 당장 급하다는 이유로 SSL 검증을 무작로 꺼버리는 개발자들이 많다.
이번에는 cURL Error 60이 왜 발생하는지, 보안을 유지하면서 정확히 문제를 해결하는 방법이 무엇인지 완벽하게 정리해서 소개하겠다.
cURL Error 60은 서버가 대상 HTTPS URL에 접속할 때 상대방의 SSL/TLS Certificate 신뢰성을 검증하지 못해서 발생한다.
브라우저는 자체적인 루트 인증서(Root CA) 목록을 내장하고 있어 웹사이트 접속 시 자동으로 신뢰성을 검증하지만, 서버 환경의 cURL 라이브러리는 로컬에 저장된 CA 번들(cacert.pem) 파일에 의존한다.
이 에러가 출력되는 대표적인 원인은 두 가지다. 첫째, 서버 OS의 CA 인증서 패키지가 오랫동안 업데이트되지 않아 최신 발급기관의 인증서를 인식하지 못하는 경우다. 둘째, PHP 설정(php.ini)에 최신 CA 인증서 경로가 올바르게 지정되지 않은 경우다.
cURL Error 60 에러를 대처하는 대중적인 접근 방법과 각각의 장단점은 다음과 같다.
| 해결 방식 | 보안 안전성 | 실무 적용 여부 | 특징 |
|---|---|---|---|
| SSL 검증 옵션 비활성화 (CURLOPT_SSL_VERIFYPEER = false) | 매우 위험 | 절대 불가 | 보안 검증을 통과하지만 중간자 공격(MITM)에 취약해짐 |
| OS 패키지 매니저로 ca-certificates 갱신 | 매우 안전 | 권장 (Linux 서버) | 시스템 전체의 최신 CA 루트 인증서 목록 일괄 업데이트 |
| 최신 cacert.pem 다운로드 후 php.ini 설정 | 매우 안전 | 권장 (Windows/독립 환경) | cURL 전용 최신 CA 번들을 직접 지정하여 깔끔하게 해결 |
문제 해결 시 가장 흔히 범하는 안일한 코딩 패턴과 이를 올바르게 교정하는 방법을 살펴본다.
에러를 없애기 위해 검증 옵션을 false로 변경하는 방식이다. 요청은 성공하지만 통신 데이터가 노출되거나 위변조될 수 있는 심각한 보안 구멍이 생긴다.
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/data");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// ✗ 보안 위험: SSL 검증을 무시함
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'cURL Error: ' . curl_error($ch);
}
curl_close($ch);
?>
Ubuntu, Debian, CentOS 등 Linux 환경이라면 시스템 전체의 CA 인증서 패키지를 최신 버전으로 업데이트하는 것으로 해결된다.
# Ubuntu / Debian 계열
sudo apt-get update
sudo apt-get install --only-upgrade ca-certificates
# CentOS / RHEL 계열
sudo yum update ca-certificates
# PHP-FPM 또는 Web Server 재시작
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx
Windows 개발 환경이나 특정 CA 번들을 지정해야 하는 경우, cURL 공식 웹사이트에서 제공하는 최신 CA 번들을 내려받아 설정한다.
1. cURL 공식 최신 인증서 파일(cacert.pem)을 다운로드한다 (https://curl.se/ca/cacert.pem).
2. 다운로드한 파일을 적절한 경로(예: C:\php\extras\ssl\cacert.pem 또는 /etc/ssl/certs/cacert.pem)로 이동시킨다.
3. php.ini 파일을 열어 curl.cainfo 및 openssl.cafile 주석을 해제하고 경로를 입력한다.
[curl]
; cURL 표준 CA 인증서 파일 경로 설정
curl.cainfo = "C:/php/extras/ssl/cacert.pem"
[openssl]
; OpenSSL 인증서 파일 경로 설정
openssl.cafile = "C:/php/extras/ssl/cacert.pem"
php.ini 설정을 완료하면 별도의 위험한 옵션 추가 없이 기본 cURL 연동 코드만으로 안전하게 통신이 완료된다.
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/data");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// SSL 검증 기본값(true) 유지
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'cURL Error: ' . curl_error($ch);
} else {
$data = json_decode($response, true);
print_r($data);
}
curl_close($ch);
?>
Array
(
[status] => success
[message] => Secure SSL connection established successfully.
)
✗ 임시방편 코드의 프로덕션 배포
로컬 환경에서 테스트할 때 시간 단축을 이유로 CURLOPT_SSL_VERIFYPEER를 false로 맞춘 뒤, 이를 Git에 그대로 커밋하여 운영 서버까지 적용하는 실수가 빈번하게 발생한다. 이는 외부 해커가 중간에서 통신 내용을 엿듣는 중간자 공격에 시스템을 방치하는 결과를 낳는다.
✓ 주기적인 CA 번들 갱신
보안 인증서 체계는 발급기관의 만료나 신규 추가에 따라 계속 변경된다. 따라서 서버 운영 시 OS 자동 업데이트 항목에 ca-certificates를 포함하거나 주기적으로 cacert.pem 파일 상태를 점검하는 정기적인 관리 체계를 갖춰야 한다.
PHP cURL Error 60은 보안 통신의 기초인 SSL 인증서 검증 체계가 정상 작동하고 있음을 반증하는 신호다. 편의성을 이유로 검증 프로세스를 끄는 행위는 보안상 매우 위험하다. 이 글의 CA 번들 업데이트 절차와 php.ini 설정을 참고해 올바른 인증서 경로를 지정하면, 서비스 보안 수준을 완벽히 지키면서 안정적인 외부 API 통신 결과를 얻을 수 있을 것이다.