- Published on
linux timeout command return code: 124·137·143 구분
- Authors

- Name
- Indo Yoon
timeout 명령은 대상 명령이 제한 시간 안에 끝나지 않으면 기본적으로 종료 코드 124를 남겨요. 반대로 명령이 스스로 실패했거나 시그널에 의해 종료됐다면 124가 아니라 그 명령의 상태 값, 또는 128에 시그널 번호를 더한 값이 그대로 올라와요. 그래서 배포·운영 스크립트에서 "시간 초과"를 정확히 잡아내려면 124를 기준으로 분기하고, --preserve-status를 썼는지 여부까지 함께 확인해야 해요.
한눈에 보기
timeout의 종료 코드는 크게 세 갈래로 나뉘어요. 첫째는 시간 초과를 알리는 고정 값, 둘째는 timeout 자체가 실패한 경우, 셋째는 대상 명령의 상태를 그대로 물려받는 경우예요.
- 시간 초과로 중단되면
--preserve-status가 없는 한 124예요. timeout이 옵션 오류 등으로 실행되지 못하면 125예요.- 대상 명령을 실행할 수 없으면 126, 찾을 수 없으면 127이에요.
- 그 밖의 경우에는 대상 명령이 반환한 코드가 그대로 전달돼요. 즉 명령이 1로 실패하면
timeout도 1을 반환해요. --preserve-status를 붙이면 시간 초과 상황에서도 명령의 실제 상태가 반환돼요. TERM으로 정리됐다면 143, KILL로 정리됐다면 137 같은 값이 나와요.
핵심은 "0이 아니면 실패"라는 단순한 판단으로는 시간 초과와 명령 자체의 실패를 구분할 수 없다는 점이에요. 124라는 값을 별도로 확인해야만 "행이 걸려서 끊겼다"는 사실을 알 수 있어요.
종료 코드 비교 표
GNU coreutils 문서와 Bash 매뉴얼의 종료 상태 설명을 기준으로 정리한 표예요.
| 종료 코드 | 의미 | 주로 나오는 상황 | 스크립트에서의 판단 |
|---|---|---|---|
| 0 | 정상 종료 | 명령이 제한 시간 안에 스스로 끝남 | 성공으로 처리 |
| 124 | 시간 초과 | --preserve-status 없이 제한 시간을 넘김 | 재시도 또는 알림 대상 |
| 125 | timeout 자체 실패 | 잘못된 옵션, 실행 환경 문제 | 스크립트 버그로 보고 수정 |
| 126 | 실행할 수 없음 | 파일은 있으나 권한이나 실행 형식에 문제가 있음 | 배포 산출물 점검 |
| 127 | 명령을 찾을 수 없음 | PATH에 없거나 이름 오타 | 실행 환경·경로 점검 |
| 137 | KILL 시그널 | --preserve-status와 KILL 조합, 또는 timeout 자체가 KILL을 받음 | 강제 종료 흔적으로 기록 |
| 143 | TERM 시그널 | --preserve-status 상태에서 TERM으로 정리됨 | 정상적인 중단 절차로 기록 |
| 그 외 | 명령의 원래 상태 | 명령이 스스로 반환한 코드 | 명령별 규칙에 따라 처리 |
표에서 눈여겨볼 부분은 124와 137·143의 관계예요. timeout은 시간 초과 시 기본적으로 124를 반환하고, --preserve-status를 켰을 때만 시그널 기반 상태 값을 드러내요. 그래서 "왜 137이 나오지?"라는 상황은 대부분 --preserve-status나 -s KILL 옵션을 함께 쓴 경우예요.
단계별로 확인하는 방법
먼저 현재 셸에서 실제로 어떤 값이 나오는지 확인해 보세요. 종료 코드는 $?로 읽으며, 다른 명령을 실행하면 곧바로 덮어써지기 때문에 반드시 대상 명령 바로 다음에 확인해야 해요.
timeout 10s sleep 300
echo $?
# 124
다음으로 명령 자체의 상태를 보고 싶다면 --preserve-status를 붙여요.
timeout --preserve-status 10s sleep 300
echo $?
# 143 (TERM으로 정리된 경우)
세 번째로, TERM을 무시하는 프로세스를 정리할 때는 --kill-after를 함께 써요. 첫 시그널을 보낸 뒤 지정한 시간이 지나도 살아 있으면 KILL을 보내요.
timeout --signal=TERM --kill-after=5s 30s ./deploy.sh
echo $?
네 번째로, 스크립트에서는 case 문으로 분기해 두는 편이 안전해요. 아래 예시는 시간 초과와 실행 환경 문제를 구분해서 로그에 남기는 형태예요.
timeout --signal=TERM --kill-after=5s 30s ./deploy.sh
status=$?
case "$status" in
0) echo "정상 종료" ;;
124) echo "제한 시간 초과로 중단" ;;
125) echo "timeout 실행 자체 실패" ;;
126) echo "명령을 실행할 수 없음" ;;
127) echo "명령을 찾을 수 없음" ;;
137) echo "KILL 시그널로 종료" ;;
143) echo "TERM 시그널로 종료" ;;
*) echo "명령이 반환한 실패 코드: $status" ;;
esac
다섯 번째로, 원인 파악이 어려울 때는 -v 옵션으로 어떤 시그널이 언제 전송됐는지 확인해요. 표준 오류로 진단 메시지가 출력돼서 124가 나온 경로를 추적하기 쉬워져요.
상황별 선택
배포 스크립트에서 무한 대기를 막는 것이 목적이라면 124만 확인하면 충분해요. 예를 들어 마이그레이션이나 헬스 체크 대기 구간에 timeout을 걸어 두고, 124가 나오면 이전 단계로 되돌리는 식이에요.
CI 파이프라인처럼 실패 원인을 구분해야 하는 경우에는 124와 127을 나눠서 봐야 해요. 124는 "오래 걸렸다"는 뜻이고, 127은 "실행 파일이 없다"는 뜻이라 대응이 완전히 달라요. 126이 나오면 산출물 권한이나 실행 형식을, 125가 나오면 timeout 호출 자체의 문법을 확인하세요.
컨테이너나 systemd 유닛처럼 프로세스를 확실히 정리해야 하는 환경이라면 --kill-after를 기본으로 붙이는 편이 좋아요. TERM을 무시하는 프로세스가 남아 있으면 다음 배포에서 포트 충돌 같은 2차 문제가 생기기 때문이에요.
반대로 명령의 실제 종료 상태를 그대로 보존해야 하는 파이프라인이라면 --preserve-status를 검토해요. 다만 이 옵션을 켜면 시간 초과 여부가 124로 드러나지 않으니, "시간 초과"와 "명령 실패"를 동시에 구분해야 하는 곳에서는 별도 래퍼나 -v 로그를 함께 쓰는 편이 좋아요.
주의할 점
가장 흔한 함정은 대상 명령이 스스로 124를 반환할 수 있다는 점이에요. timeout이 남긴 124인지 명령이 만든 124인지 종료 코드만으로는 구분되지 않아요. 이 모호함을 없애려면 실행 로그에 -v 출력을 함께 남기거나, 래퍼 스크립트에서 시간 초과 시에만 특정 마커 파일을 만들도록 구성하세요.
파이프라인을 다룰 때도 주의가 필요해요. timeout은 자신이 직접 실행한 자식 프로세스를 기준으로 시그널을 보내기 때문에, 셸 파이프는 sh -c로 묶어서 넘기는 편이 안전해요.
timeout 30s sh -c 'dump-db | gzip > backup.gz'
--foreground 옵션을 쓰면 TTY 제어와 시그널 전달 방식이 달라지고, 자식 프로세스가 시간 초과 대상에서 빠져요. 대화형 프롬프트를 다뤄야 하는 특수한 경우가 아니라면 기본 동작을 유지하는 편이 예측하기 쉬워요.
셸 내장 명령은 timeout의 대상이 될 수 없어요. 내장 명령을 제한 시간과 함께 쓰려면 bash -c로 감싸서 별도 프로세스로 띄워야 해요.
set -e가 걸린 스크립트에서는 0이 아닌 모든 코드가 즉시 종료로 이어져요. 시간 초과를 정상적인 흐름의 하나로 처리하고 싶다면 if 문이나 || true로 감싸서 분기 지점을 명시하세요.
자주 묻는 질문
timeout이 124를 반환하는 이유는 무엇인가요?
GNU coreutils 문서가 "명령이 시간 초과되면 --preserve-status가 없는 한 124로 종료한다"고 규정하고 있기 때문이에요. 즉 124는 대상 명령의 상태가 아니라 timeout이 시간 초과를 알리기 위해 예약해 둔 값이에요. 그래서 명령이 실제로 어떤 코드를 냈는지는 124만 보고는 알 수 없어요.
137과 143은 각각 무엇을 뜻하나요?
128에 시그널 번호를 더한 형태로, KILL은 137, TERM은 143이에요. --preserve-status를 켠 상태에서 시간 초과로 정리되면 이 값이 그대로 반환돼요. 옵션 없이 시간 초과가 발생하면 시그널 종류와 관계없이 124가 나온다는 점을 기억하세요.
스크립트에서 시간 초과와 명령 실패를 구분하려면 어떻게 하나요?
--preserve-status 없이 실행하고 124를 별도 분기로 처리하는 방법이 가장 단순해요. 명령의 실제 상태까지 함께 봐야 한다면 -v 출력이나 별도 마커를 로그에 남겨서 두 정보를 동시에 확보하세요. 종료 코드 하나만으로 두 가지를 모두 판별하려 하면 경계 상황에서 오판이 생겨요.
127이 나오면 무엇을 확인해야 하나요?
timeout이 대상 명령을 찾지 못했다는 뜻이에요. PATH 설정, 실행 파일 이름의 오타, 컨테이너 이미지에 바이너리가 포함됐는지를 차례로 확인하세요. 배포 스크립트라면 실행 경로를 절대 경로로 고정해 두는 것만으로도 이 오류를 크게 줄일 수 있어요.
시간 지정 값에 접미사를 붙이지 않으면 어떻게 되나요?
접미사 없이 숫자만 쓰면 기본 시간 단위로 해석돼요. s, m, h, d 접미사를 지원하고 소수점 표기도 가능하니, 운영 스크립트에서는 단위를 명시해 두는 편이 읽기 좋아요. 정확한 문법과 옵션 목록은 아래 출처의 공식 문서에서 확인할 수 있어요.
출처
- GNU Coreutils 매뉴얼, timeout 실행과 종료 상태: https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html
- GNU Bash 매뉴얼, 종료 상태와 시그널 기반 코드: https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
- timeout(1) 매뉴얼 페이지: https://man7.org/linux/man-pages/man1/timeout.1.html
배포 스크립트에서 실패 원인을 코드로 구분하는 방식이 궁금하다면 종료 코드로 보는 배포 실패 원인 정리 글도 함께 보시면 좋아요. 서비스 재시작이 반복될 때 확인할 항목을 정리한 systemd 재시작 실패 점검 순서도 운영 중 장애 대응에 도움이 돼요.