대용량 데이터를 동기식으로 처리하거나 외부 API를 대량 호출할 때 화면에 PHP Fatal error: Maximum execution time of 30 seconds exceeded 에러가 발생하며 작업이 멈춘 경험이 있을 것이다.
다만 단순 타임아웃 증가로 피할 수 있는지 아니면 코드의 구조적 문제인지 정확한 원인이나 해결책을 모르는 경우가 많다.
이번에는 이 에러가 정확히 뭔지, 왜 필요한지, 그리고 서버 설정과 코드 최적화를 통해 어떻게 해결하는지 완벽하게 정리해서 소개하겠다.
PHP는 기본적으로 개별 웹 요청이 서버 리소스를 독점하는 것을 막기 위해 스크립트 최대 실행 시간을 제한한다. 이 제한을 담당하는 설정값이 바로 php.ini의 max_execution_time이다.
기본값은 30초로 설정되어 있으며, 스크립트가 30초를 초과하여 실행되면 PHP 엔진이 강제로 프로세스를 중단시키며 런타임 에러를 출력한다.
| 원인 분류 | 발생 상황 | 해결 방향 |
|---|---|---|
| 서버 설정 제한 | 기본 설정값(30초)보다 오래 걸리는 긴 배치 작업 | php.ini 또는 set_time_limit()을 통한 타임아웃 연장 |
| 외부 통신 병목 | API 응답 지연 또는 무한 대기(cURL 타임아웃 미설정) | cURL 타임아웃 옵션 추가 및 소켓 타임아웃 제한 |
| 비효율적인 루프 | 대량의 데이터베이스 조회 및 내부 중첩 반복문 | 청크(Chunk) 단위 데이터 처리 및 메모리/시간 분할 |
이 문제를 해결하는 가장 직관적인 방법은 PHP 실행 제한 시간을 늘려주는 것이다. 작업 환경과 권한에 따라 세 가지 방법 중 선택하여 적용할 수 있다.
서버 관리자 권한이 있다면 php.ini 파일에서 직접 해당 값을 변경할 수 있다. 수정 후에는 반드시 PHP-FPM이나 Apache 웹서버를 재시작해야 적용된다.
; php.ini 수정
max_execution_time = 300 ; 30초에서 300초(5분)로 변경특정 대용량 작업이 실행되는 파일 상단에만 시간을 연장하고 싶다면 set_time_limit() 함수를 사용한다. 0을 입력하면 실행 시간 제한이 무제한으로 변경된다.
<?php
// 스크립트 최상단에 추가
set_time_limit(300); // 실행 시간을 300초로 설정
?>php.ini를 수정할 수 없는 공유 호스팅 환경에서는 .htaccess 파일에 php_value를 추가하거나 .user.ini를 작성하여 적용할 수 있다.
# .htaccess 파일에 추가하는 경우
php_value max_execution_time 300
무작정 execution time을 늘리는 것은 서버 전체의 안정성을 저해할 수 있다. 코드 내부에서 루프가 돌 때마다 시간을 리셋하거나 데이터를 분할 처리하는 방법이 훨씬 안전하다.
✗ 잘못된 방식: 모든 데이터를 한 번에 가져와 오래 걸리는 동기 작업 처리
<?php
// ✗ 모든 회원에게 메일을 발송하는 루프 (30초 초과 시 에러 발생)
$users = $db->query("SELECT * FROM users")->fetchAll();
foreach ($users as $user) {
// 외부 SMTP 통신으로 30초를 쉽게 넘김
send_email($user['email']);
}
?>✓ 올바른 방식: 루프 내 execution time 리셋 및 청크 단위 데이터 처리
<?php
// ✓ 반복할 때마다 타이머를 리셋하고 메모리를 효율적으로 관리
$page = 0;
$limit = 100;
do {
// 반복문 실행 시작 시마다 30초의 추가 시간을 부여
set_time_limit(30);
$offset = $page * $limit;
$users = $db->query("SELECT * FROM users LIMIT {$limit} OFFSET {$offset}")->fetchAll();
foreach ($users as $user) {
send_email($user['email']);
}
$page++;
} while (count($users) === $limit);
?>출력/결과: 30초 타임아웃 중단 없이 만 명 이상의 사용자 데이터도 안전하게 순차 처리 완료.
✗ 잘못된 접근: set_time_limit(0)을 설정해 두고 모든 문제를 해결했다고 방치하는 경우.
무제한 실행은 스크립트가 무한 루프에 빠졌을 때 PHP 프로세스가 서버 CPU와 메모리를 점유한 채 종료되지 않는 치명적인 결과를 초래한다.
✓ 올바른 접근: 웹 요청(HTTP) 처리 시에는 최대 30~60초 이내로 유지하고, 수분 이상 소요되는 대용량 데이터 작업은 CLI(Command Line Interface) 환경의 크론잡(Cron)이나 비동기 큐 시스템으로 분리해야 한다.
또한 Nginx와 FastCGI(PHP-FPM)를 사용할 경우, PHP 타임아웃을 300초로 늘려도 Nginx의 fastcgi_read_timeout 설정이 60초라면 Nginx에서 504 Gateway Timeout 에러가 먼저 발생한다는 점을 명심해야 한다.
PHP Maximum execution time exceeded 에러는 서버 리소스를 보호하기 위한 안전장치가 작동했음을 의미한다. 무조건 타임아웃 시간만 늘리는 대증요법보다는 작업의 성격에 맞춰 데이터 청크 분할 및 runtime 시간 리셋을 적용하는 것이 바람직하다. 이 글의 3단계 청크 처리 예제 코드를 참고해 스크립트를 개선하면 타임아웃 에러 없이 안정적으로 대용량 작업을 완료할 수 있을 것이다.