- Published on
systemd 서비스가 자동재시작 안 될 때 7가지 원인
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 Restart=always 를 걸어놨는데도 서비스가 죽은 채로 멈춰 있으면, 대개 “systemd가 재시작을 못 하는” 게 아니라 “재시작 조건이 충족되지 않거나, 이미 재시작을 포기한 상태”인 경우가 많습니다. 이 글은 systemd가 자동 재시작을 하지 않는 대표 원인 7가지를 로그로 확인하는 방법과 unit 파일에서 고치는 방법 중심으로 정리합니다.
먼저 공통으로 확인할 기본 진단 명령부터 잡고 들어가겠습니다.
# 서비스 상태와 최근 종료 코드/원인
systemctl status myapp.service -l
# 저널 로그(부팅 이후)
journalctl -u myapp.service -b --no-pager
# 재시작/실패 카운터 포함한 상세 속성
systemctl show myapp.service \
-p ActiveState -p SubState -p Result \
-p ExecMainStatus -p ExecMainCode \
-p NRestarts -p Restart -p RestartUSec \
-p StartLimitIntervalUSec -p StartLimitBurst
# unit 파일과 drop-in 오버라이드 확인
systemctl cat myapp.service
# systemd가 실제로 어떻게 해석했는지(문법/경로/권한 등)
systemd-analyze verify /etc/systemd/system/myapp.service
1) Restart= 정책이 상황과 안 맞는다
가장 흔한 케이스는 Restart=on-failure 를 써놓고, 프로세스가 “실패”가 아닌 방식으로 종료되는 경우입니다.
자주 헷갈리는 종료 유형
- 정상 종료: exit code
0(systemd 관점에서 성공) - SIGTERM 등 신호로 종료: systemd 설정에 따라 성공/실패로 분류가 달라질 수 있음
- 워치독/타임아웃/킬:
Result=timeout또는Result=signal등으로 찍힘
Restart=on-failure 는 비정상 종료(실패) 에만 반응합니다. 애플리케이션이 스스로 exit 0 으로 내려가거나, SuccessExitStatus= 로 성공으로 간주되면 재시작이 안 됩니다.
해결
정말 “어떤 이유로든 내려가면 다시 올려야” 한다면 Restart=always 또는 Restart=on-abnormal 를 검토합니다.
[Service]
Restart=always
RestartSec=3
반대로, 특정 exit code는 정상 종료로 보고 재시작하지 않게 하고 싶다면:
[Service]
Restart=on-failure
SuccessExitStatus=0 143
# 143은 SIGTERM(15) 종료를 애플리케이션이 128+15로 반환하는 패턴에서 자주 보임
2) 서비스 타입이 안 맞아 systemd가 “이미 끝났다”고 판단한다
Type= 이 잘못되면 systemd가 프로세스를 추적하지 못해 재시작 조건이 꼬입니다.
대표 증상
Type=oneshot인데 데몬처럼 계속 떠 있어야 하는 서비스Type=forking인데 실제로 fork 하지 않고 foreground로 도는 프로세스Type=simple인데 실제 실행은 백그라운드로 보내고 부모가 바로 종료되는 스크립트
예를 들어 ExecStart=/path/run.sh 가 내부에서 nohup ... & 로 백그라운드 실행 후 스크립트가 종료되면, systemd는 메인 프로세스가 끝났다고 보고 상태를 exited 로 둡니다. 이후 죽어도 감지 자체가 안 되니 재시작도 없습니다.
해결
- 가능하면 애플리케이션을 foreground 로 실행
Type=simple또는 필요 시Type=exec사용
[Service]
Type=simple
ExecStart=/usr/bin/java -jar /opt/myapp/app.jar
Restart=always
forking 데몬이라면 PIDFile을 정확히 제공해야 합니다.
[Service]
Type=forking
PIDFile=/run/mydaemon.pid
ExecStart=/usr/sbin/mydaemon --pidfile=/run/mydaemon.pid
Restart=on-failure
3) StartLimit* 에 걸려 “재시작 포기” 상태다
systemd는 짧은 시간에 너무 자주 실패하면 재시작을 멈춥니다. 이때 상태는 보통 Start request repeated too quickly 로 찍힙니다.
확인
systemctl status myapp.service -l
systemctl show myapp.service -p StartLimitIntervalUSec -p StartLimitBurst -p NRestarts
journalctl -u myapp.service -b --no-pager | tail -n 200
해결
- 실패 루프를 끊고(원인 해결) 다시 올리기:
systemctl reset-failed myapp.service - 정책 조정:
StartLimitIntervalSec와StartLimitBurst변경
[Unit]
StartLimitIntervalSec=60
StartLimitBurst=10
[Service]
Restart=on-failure
RestartSec=5
운영에서 무작정 burst를 크게 올리면 “장애를 더 빨리 증폭”시킬 수 있습니다. DB 장애나 외부 의존성 장애로 계속 재시작하면서 커넥션/스레드가 폭증하는 패턴도 흔합니다. 비슷한 장애 전개는 다음 글의 진단 방식이 참고됩니다.
4) RestartPreventExitStatus= 또는 RestartForceExitStatus= 가 의도치 않게 작동한다
systemd는 exit code나 시그널에 따라 “재시작을 막거나/강제”할 수 있습니다. 이전에 누군가 튜닝해둔 값이 의도치 않게 재시작을 차단하는 경우가 있습니다.
확인
systemctl show myapp.service \
-p RestartPreventExitStatus -p RestartForceExitStatus \
-p SuccessExitStatus
예시: 특정 종료 코드는 재시작하지 않게 막기
[Service]
Restart=on-failure
RestartPreventExitStatus=2
애플리케이션이 예외 상황에서 exit code 2 를 반환하도록 되어 있다면, 이 설정 때문에 재시작이 안 됩니다.
해결
- exit code 정책을 문서화하고 서비스 코드와 합의
- 불필요한
RestartPreventExitStatus=제거
5) Watchdog/Timeout 설정으로 “죽긴 죽는데 재시작 안 하는 것처럼” 보인다
TimeoutStartSec / TimeoutStopSec / WatchdogSec 는 잘못 설정하면 다음처럼 보일 수 있습니다.
- 시작이 느린데
TimeoutStartSec이 너무 짧아 timeout으로 kill - stop이 느린데
TimeoutStopSec이 짧아 SIGKILL로 종료 WatchdogSec을 켰는데 애플리케이션이 notify를 안 해서 watchdog timeout
이 경우 재시작은 시도되지만, 계속 timeout으로 죽어서 StartLimit에 걸려 “어느 순간부터” 재시작이 멈춘 것처럼 보입니다.
확인 포인트
journalctl 에서 timed out , watchdog , killed 키워드를 찾습니다.
journalctl -u myapp.service -b --no-pager | egrep -i 'timeout|watchdog|killed' \
| tail -n 100
해결 예시
[Service]
TimeoutStartSec=120
TimeoutStopSec=60
Restart=on-failure
RestartSec=5
Type=notify 와 watchdog를 쓸 거면 애플리케이션이 sd_notify 를 호출해야 합니다. Java라면 별도 라이브러리/래퍼가 필요하고, 미구현 상태에서 Type=notify 만 켜면 시작 판정부터 꼬일 수 있습니다.
6) 실제로는 재시작 중인데, 의존성/환경 문제로 즉시 실패한다
재시작이 “안 되는” 게 아니라 “되자마자 또 죽는” 경우입니다. 특히 다음 유형이 흔합니다.
- 포트가 이미 점유됨: 이전 프로세스가 좀비처럼 남았거나, 다른 프로세스가 사용
- 파일 디스크립터/메모리 제한:
LimitNOFILE, cgroup 메모리 제한 - 런타임 환경 누락:
WorkingDirectory,EnvironmentFile경로 오류 - 권한 문제:
User=로 실행 시 접근 불가
확인
# 최근 N회 재시작 흔적
systemctl show myapp.service -p NRestarts
# 종료 코드가 반복되는지
systemctl status myapp.service -l
# 실행 환경/경로
systemctl show myapp.service -p User -p Group -p WorkingDirectory -p EnvironmentFiles
# 리소스 제한
systemctl show myapp.service -p LimitNOFILE -p MemoryMax -p TasksMax
해결 예시: 환경 파일/작업 디렉터리 명시
[Service]
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=3
외부 의존성 장애(예: Redis, DB, gRPC)로 인해 애플리케이션이 부팅 직후 종료하는 경우도 많습니다. 이때는 systemd 재시작만으로는 해결이 안 되고, 애플리케이션 레벨에서 backoff/retry, readiness 분리, 타임아웃 정책 정리가 필요합니다.
7) unit 파일 변경 후 daemon-reload 누락, drop-in 충돌, 혹은 다른 매니저가 덮어쓴다
설정을 바꿨는데도 동작이 그대로라면, systemd가 변경을 반영하지 못했거나 다른 설정이 우선 적용된 경우가 있습니다.
흔한 패턴
/etc/systemd/system/myapp.service수정 후systemctl daemon-reload안 함systemctl edit myapp.service로 만든 drop-in 이 원본을 덮어씀- 배포 도구/컨테이너/프로세스 매니저가 별도로 기동하여 systemd 서비스와 충돌
확인
# systemd가 보는 최종 unit 구성(원본+drop-in)
systemctl cat myapp.service
# unit 파일 로드 시각/경로 추적
systemctl show myapp.service -p FragmentPath -p DropInPaths
해결 절차(안전한 순서)
# 1) 변경 반영
sudo systemctl daemon-reload
# 2) 실패 상태 초기화(StartLimit에 걸린 경우 특히 중요)
sudo systemctl reset-failed myapp.service
# 3) 재시작
sudo systemctl restart myapp.service
# 4) 재부팅 후에도 자동 시작 확인
sudo systemctl enable myapp.service
실전 점검 체크리스트(한 번에 훑기)
아래 항목만 순서대로 확인해도, 대부분의 “자동 재시작이 안 됨” 이슈는 빠르게 좁혀집니다.
# 1) 재시작 정책과 실패 판정
systemctl show myapp.service -p Restart -p SuccessExitStatus
# 2) 최근 실패 원인
systemctl status myapp.service -l
journalctl -u myapp.service -b --no-pager | tail -n 200
# 3) StartLimit에 걸렸는지
systemctl show myapp.service -p StartLimitIntervalUSec -p StartLimitBurst -p NRestarts
# 4) 타입/추적 문제
systemctl show myapp.service -p Type -p MainPID -p ExecMainStatus -p ExecMainCode
# 5) 최종 unit 구성(오버라이드 포함)
systemctl cat myapp.service
권장 unit 템플릿(웹/백엔드 서비스용)
서비스가 foreground로 실행되고, 실패 시 재시작하며, 과도한 재시작은 완화하는 “무난한” 예시입니다.
[Unit]
Description=MyApp Service
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=6
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/java -jar /opt/myapp/app.jar
Restart=on-failure
RestartSec=5
TimeoutStartSec=120
TimeoutStopSec=60
# 필요 시 리소스 제한(환경에 맞게 조정)
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
마무리
systemd 자동 재시작이 “안 된다”는 현상은 대부분 다음 중 하나로 귀결됩니다: 재시작 조건 불일치, 프로세스 추적 실패(Type), StartLimit에 의한 포기, 혹은 재시작은 되지만 즉시 같은 이유로 재실패. systemctl show 와 journalctl 로 종료 코드/Result/재시작 카운터를 먼저 확보하면, 감으로 설정을 바꾸는 시간을 크게 줄일 수 있습니다.