PHP로 외부 API와 통신할 때 cURL 연동을 진행하다가 서버 프로세스가 무한정 대기 상태에 빠지거나 웹서버가 응답하지 않고 멈추는 현상을 경험해봤을까?
다만 대부분의 개발자들은 이를 단순 네트워크 지연이나 상대방 서버 문제로만 치부하고, 정확한 타임아웃 옵션의 차이를 모른 채 기본 설정으로 cURL을 전송하는 경우가 많다.
이번에는 cURL 타임아웃 설정이 정확히 뭔지, 왜 서버 안정성에 필수적인지, 그리고 CURLOPT_CONNECTTIMEOUT과 CURLOPT_TIMEOUT 옵션을 제대로 활용하는 법을 완벽하게 정리해서 소개하겠다.
PHP의 cURL 확장 모듈은 외부 서버와 HTTP/HTTPS 통신을 주고받을 때 가장 널리 사용되는 수단이다. 하지만 타임아웃을 명시적으로 지정하지 않으면 상대방 서버가 다운되었거나 네트워크 패킷이 분실되었을 때 PHP-FPM 워커 프로세스가 대기(Hanging) 상태로 유지된다.
이런 미응답 프로세스가 누적되면 결국 전체 PHP 워커가 고갈되어 502 Bad Gateway 에러나 서버 전체 다운으로 이어지게 된다. cURL에서 타임아웃을 다룰 때는 반드시 다음 두 가지 핵심 옵션을 구분하여 이해해야 한다.
| 옵션명 | 설명 | 권장 설정 범위 |
|---|---|---|
| CURLOPT_CONNECTTIMEOUT | 상대방 서버와의 TCP 핸드셰이크(연결 확립)까지 기다리는 최대 시간(초) | 2초 ~ 5초 |
| CURLOPT_TIMEOUT | cURL 함수 실행 시작부터 전체 데이터 응답 수신이 완료될 때까지의 최대 시간(초) | 5초 ~ 30초 |
cURL은 일반적인 초 단위 설정 외에도, 1초 미만의 고성능/저지연 통신을 위한 밀리초(ms) 단위 설정 옵션을 함께 제공한다.
대규모 트래픽이 발생하는 서비스나 타이트한 SLA(서비스 수준 협약)가 적용된 Microservice Architecture에서는 밀리초 옵션을 활용하는 것이 훨씬 유리하다.
- 초 단위 옵션: CURLOPT_CONNECTTIMEOUT, CURLOPT_TIMEOUT
- 밀리초 단위 옵션: CURLOPT_CONNECTTIMEOUT_MS, CURLOPT_TIMEOUT_MS
예를 들어 외부 API 접속 시도를 최대 500ms(0.5초)까지만 허용하고 전체 응답을 2000ms(2초) 이내로 제한하고 싶다면 밀리초 단위 옵션을 선택해야 한다.
다음은 실제 개발 실무에서 바로 사용할 수 있도록 타임아웃과 예외 처리가 철저하게 반영된 cURL 연동 코드 예시다.
✗ 잘못된 코드 (타임아웃 미설정으로 인한 무한 대기 위험):
<?php
// 타임아웃 설정이 전혀 없어 상대 서버 장애 시 PHP 프로세스가 무한정 대기함
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.example.com/v1/data");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>✓ 올바른 코드 (연결 및 전체 타임아웃 및 에러 검증이 포함된 예제):
<?php
function call_api_safely(string $url, int $connectTimeout = 3, int $totalTimeout = 10): array {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $connectTimeout, // 연결 시도 제한 (3초)
CURLOPT_TIMEOUT => $totalTimeout, // 전체 응답 완료 제한 (10초)
CURLOPT_FAILONERROR => false,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 타임아웃 에러 처리 (CURLE_OPERATION_TIMEDOUT = 28)
if ($errno === CURLE_OPERATION_TIMEDOUT) {
return [
'success' => false,
'code' => 'TIMEOUT',
'message' => 'API 요청 시간이 초과되었습니다.'
];
} elseif ($errno !== 0) {
return [
'success' => false,
'code' => 'CURL_ERROR',
'message' => "cURL 오류 ({$errno}): {$errmsg}"
];
}
return [
'success' => ($httpCode >= 200 && $httpCode < 300),
'http_code' => $httpCode,
'data' => json_decode($response, true)
];
}
// 실행 예시
$result = call_api_safely('https://httpbin.org/delay/2', 3, 5);
print_r($result);
?>결과 출력 예시 (정상 수신 시):
{
"success": true,
"http_code": 200,
"data": {
"url": "https://httpbin.org/delay/2"
}
}
타임아웃 옵션을 적용할 때 개발자들이 자주 범하는 실수들과 해결법을 점검해 보자.
✗ 잘못된 것: CURLOPT_TIMEOUT만 지정하고 CURLOPT_CONNECTTIMEOUT을 생략하는 경우
상대방 서버의 IP가 여러 개 등록된 DNS 환경이거나 방화벽 거부(Drop) 상태일 경우, 연결 타임아웃이 없으면 OS 수준의 TCP 타임아웃(기본 75초 이상)이 발생하여 전체 프로세스가 지연된다.
✓ 올바른 것: 두 옵션을 항상 한 쌍으로 설정하되, CONNECTTIMEOUT <= TIMEOUT 관계를 유지하는 것
접속 시간 제한은 전체 실행 시간 제한보다 짧거나 같아야만 논리적 충돌 없이 정상 작동한다.
✗ 잘못된 것: Linux 환경에서 CURLOPT_TIMEOUT_MS 사용 시 즉시 실패 에러가 발생하는 경우
libcurl이 DNS 조회 시 신호(Signal)를 사용할 때 밀리초 단위 타임아웃 설정이 무시되거나 에러가 반환될 수 있다.
✓ 올바른 것: 밀리초(MS) 단위 옵션을 사용할 때는 CURLOPT_NOSIGNAL 옵션을 true(1)로 함께 설정한다.
<?php
// 밀리초 타임아웃 사용 시 필수 설정
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 500);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 2000);
?>
PHP cURL 외부 API 요청 멈춤 해결은 외부 서비스의 장애나 네트워크 지연이 내 서비스로 전이되는 것을 차단하는 핵심적인 서버 방어 기법이다. 적절한 타임아웃 옵션을 명시하는 작은 습관이 모여서 웹서버 전체의 가용성과 서비스 연동 안정성을 획기적으로 향상시킨다는 점을 잊지 말자. 이 글의 실전 예제 코드를 참고해 현재 운영 중인 서비스의 외부 통신 모듈에 타임아웃 설정을 점검하고 반영하면, 연쇄적인 서버 다운 사고를 사전에 완벽하게 예방할 수 있을 것이다.