증상과 정확한 오류
systemd TimeoutStopSec 문제는 서비스 중지나 재시작을 요청한 뒤 오랫동안 deactivating (stop-sigterm) 상태가 이어지고, 마지막에 State 'stop-sigterm' timed out. Killing. 또는 SIGKILL 관련 로그가 남는 형태로 나타납니다. 약 90초 뒤 반복된다면 해당 unit의 종료 제한 시간이 90초인지 먼저 확인해야 합니다.
systemd는 일반적으로 서비스 종료 시 KillSignal에 설정된 신호를 보내고, 기본값은 SIGTERM입니다. 프로세스가 제한 시간 안에 끝나지 않으면 SendSIGKILL=yes인 일반 구성에서 남은 프로세스를 SIGKILL로 정리합니다. 따라서 마지막 SIGKILL은 최초 원인이 아니라, 정상 종료가 시간 안에 완료되지 않았다는 결과입니다.
TimeoutStopSec만 크게 늘리면 데이터 flush나 작업 drain에 실제로 시간이 필요한 서비스는 살릴 수 있지만, SIGTERM handler가 멈췄거나 ExecStop이 반환하지 않는 문제를 가릴 수도 있습니다. 먼저 unit의 종료 명령과 애플리케이션 로그를 같은 시각으로 맞춰 어느 단계가 시간을 쓰는지 확인합니다.
같은 2초 제한에서 확인한 종료 결과
2026-09-06 KST에 별도 Linux 호스트의 systemd 255 (255.4-1ubuntu8.17)에서 임시 unit 두 개를 실행했습니다. 운영 서비스 대신 20초 수명 제한을 둔 Python 프로세스를 사용했고, 두 unit 모두 TimeoutStopSec=2s, KillMode=control-group, SendSIGKILL=yes였습니다. 신호 처리기만 달리했으며 실험 후 unit을 정리했습니다.
SIGTERM ignored: stop took 2.150 s, systemctl stop exit=0
State 'stop-sigterm' timed out. Killing.
Main process exited, code=killed, status=9/KILL
Failed with result 'timeout'.
SIGTERM handled: stop took 0.033 s, systemctl stop exit=0
Deactivated successfully.
중요한 차이는 systemctl stop의 종료 코드가 두 경우 모두 0이었다는 점입니다. 명령 성공만으로 애플리케이션이 정상 종료했다고 판정하면 강제 종료를 놓칠 수 있습니다. journal의 timeout, 9/KILL과 종료 완료 기록을 함께 확인해야 합니다. 이 실험은 신호 처리 경계만 보여 주며 실제 데이터베이스 flush, worker drain 또는 90초 운영 장애를 재현한 것은 아닙니다.
먼저 확인할 값
| 확인 지점 | 명령 또는 속성 | 판단 기준 |
|---|---|---|
| 적용된 종료 제한 | systemctl show app.service -p TimeoutStopUSec | 실제 적용값이 1min 30s라면 관찰한 90초와 일치하는지 봅니다. |
| 종료 신호 정책 | systemctl show app.service -p KillMode -p KillSignal -p SendSIGKILL | 기본적인 control-group, SIGTERM, SIGKILL 후속 정리 여부를 확인합니다. |
| unit 원문과 drop-in | systemctl cat app.service | vendor unit보다 뒤에 적용된 TimeoutStopSec, ExecStop, KillMode를 찾습니다. |
| 중지 타임라인 | journalctl -u app.service --since '-10 min' -o short-precise | 중지 요청, SIGTERM 처리 시작, drain 완료, timeout, SIGKILL 시각을 순서대로 비교합니다. |
| 남은 프로세스 | systemctl status app.service | main process보다 child process가 남는지, ExecStop 프로세스가 멈췄는지 상태 트리로 확인합니다. |
app.service는 실제 unit 이름으로 바꿉니다. 설정 파일에 90초가 적혀 있지 않아도 manager의 DefaultTimeoutStopSec를 상속할 수 있습니다. 반대로 unit 또는 drop-in이 값을 재정의했다면 배포판 기본값을 추측하지 말고 systemctl show 결과를 기준으로 판단합니다.
원인 분기
systemd TimeoutStopSec 만료 전후
- SIGTERM handler가 실행되지 않음: 애플리케이션이 다른 신호만 처리하거나 PID 1 래퍼가 신호를 자식에게 전달하지 않으면 종료 로그 자체가 시작되지 않습니다. 컨테이너 서비스라면 init·entrypoint 계층도 확인합니다.
- handler가 시작됐지만 끝나지 않음: 네트워크 요청, 큐 drain, 파일 flush, worker join이 무기한 대기하는 경우입니다. 종료 로그의 마지막 단계와 열린 작업 수를 함께 봅니다.
- ExecStop이 동기적으로 끝나지 않음: 정지 스크립트가 실제 종료를 기다리지 않거나 반대로 응답 없는 관리 endpoint를 계속 기다릴 수 있습니다.
ExecStop실행 시간과 본 프로세스 종료 시간을 분리합니다. - 자식 프로세스만 남음: 기본
KillMode=control-group은 unit의 프로세스 그룹 전체를 정리합니다.KillMode=process처럼 main process만 다루도록 바꾸면 자식이 남을 수 있으며 일반적인 해결책으로 권장하기 어렵습니다. - 정상 drain 시간이 제한보다 김: 진행률이 계속 변하고 제한을 조금 넘기는 경우에는 합리적인 상한으로
TimeoutStopSec를 늘릴 수 있습니다.Type=notify서비스가 종료 연장을 지원한다면 정해진 규약으로 제한 시간을 연장하는 설계도 검토합니다.
수정 순서
- 현재 unit과 상태를 보관합니다.
systemctl cat app.service > app.service.before-stop-fix.txt,systemctl show app.service > app.service.before-stop-fix.properties를 실행하고 최근 journal을 별도 파일로 저장합니다. - 유지보수 시간에 중지 과정을 한 번 측정합니다.
systemctl stop app.service를 실행한 시각부터 timeout까지 journal을 확인합니다. 운영 요청을 받는 서비스라면 먼저 트래픽을 빼고 진행하며, 강제 kill을 별도로 반복하지 않습니다. - 애플리케이션 종료 경로를 먼저 고칩니다. SIGTERM을 받으면 신규 작업 수락을 멈추고, 진행 중 작업에 상한을 두고, 자원을 닫은 뒤 main process가 종료되도록 합니다. 래퍼 스크립트가 있다면
exec로 실제 프로세스를 넘겨 신호가 올바른 PID에 도달하게 합니다. - ExecStop의 역할을 최소화합니다. 애플리케이션이 SIGTERM만으로 종료할 수 있다면 불필요한 장시간
ExecStop을 제거합니다. 별도 정지 명령이 필요하면 명령이 실제 서비스 종료를 동기적으로 기다리고 실패 시 명확한 종료 코드를 반환하도록 합니다. - 정상 drain에만 제한 시간을 맞춥니다. 측정상 90초보다 조금 더 필요하고 진행이 확인될 때 drop-in에
[Service]와TimeoutStopSec=3min처럼 근거 있는 상한을 둡니다. 무한 대기나TimeoutStopSec=infinity로 문제를 숨기지 않습니다. - 설정을 검증하고 반영합니다.
systemd-analyze verify app.service로 unit을 검사한 뒤systemctl daemon-reload를 실행합니다. 유지보수 시간에 start와 stop을 다시 수행하고 새 제한 시간 안에 SIGTERM 종료가 완료되는지 확인합니다.
검증과 롤백
성공 기준은 SIGKILL 로그가 사라지고, 서비스가 정해진 시간 안에 자체 종료 완료 로그를 남기는 것입니다. systemctl show app.service -p Result -p ExecMainStatus -p ActiveState -p SubState에서 중지 뒤 실패 결과가 남지 않는지 확인하고, 다시 시작했을 때 데이터 복구나 미완료 작업 재처리가 불필요한지도 봅니다.
- 중지 직전의 진행 중 작업 수와 종료 직후 완료·재시도 수가 일치하는지 확인합니다.
journalctl -u app.service -b에 timeout과 SIGKILL 메시지가 다시 나타나지 않는지 봅니다.systemctl status app.service에서 unit 종료 뒤 자식 프로세스가 남지 않는지 확인합니다.- 새 timeout이 장애 복구 시간을 지나치게 늘리지 않는지 실제 stop 소요 시간을 기록합니다.
변경 뒤 종료가 더 느려지거나 재시작이 실패하면 만든 drop-in을 백업본과 비교해 이번 변경만 되돌리고 systemctl daemon-reload를 실행합니다. 원래 설정으로 돌아온 뒤에도 SIGTERM 로그가 전혀 없다면 timeout 값을 계속 조정하지 말고 실행 래퍼와 애플리케이션 signal handler를 다시 확인합니다.
같이 확인할 글
공식 문서 및 확인 기준
- systemd.service manual — TimeoutStopSec가 ExecStop과 서비스 종료에 적용되는 범위 및 만료 후 SIGKILL 동작
- systemd.service source manual — 서비스 종료 제한 시간이 ExecStop과 본 프로세스에 적용되고 연장될 수 있는 구현 문서
- systemd.kill source manual — KillMode와 KillSignal에 따른 프로세스 그룹 종료 순서
이 글에 남아 있던 이전 검증 기록
작성: Nole IT 편집팀
검증 수준: 공식 문서 대조
종료 신호 순서와 unit 속성을 현재 systemd 서비스 및 kill 문서와 대조했습니다. 제한 시간을 바꾸기 전에 장애 호스트의 실제 적용값을 확인하세요.