Published on

systemd 서비스가 재부팅 후 안 뜰 때 9분 진단

Authors

서버 재부팅 후 “서비스가 안 떠요”는 흔하지만, 원인은 의외로 몇 가지 범주로 수렴합니다. 핵심은 부팅 시점에 systemd가 무엇을 시도했고, 왜 실패했는지를 짧은 시간 안에 재현 가능한 증거로 모으는 것입니다.

이 글은 9분 타임박스로 진단하는 흐름으로 구성했습니다. 각 단계는 명령 1~3개로 결론을 내리거나, 다음 단계로 넘길 근거를 만드는 데 집중합니다.

0분: 증상 분류부터 ("안 뜸"의 의미)

아래 중 무엇인지 먼저 분류하세요.

  1. 서비스 유닛이 아예 안 보임: systemctl status my.serviceUnit my.service could not be found 비슷하게 나옴
  2. 유닛은 보이는데 inactive: inactive (dead)
  3. 유닛은 보이는데 failed: failed (Result: exit-code)
  4. running인데 기능이 안 됨: 포트 미오픈, 의존 리소스 실패, 권한 문제

이 분류가 되면, 로그와 설정을 어디서 봐야 할지 바로 좁혀집니다.

1분: 유닛 존재/이름/경로 확인

"재부팅 후 안 뜬다"에서 가장 먼저 터지는 건 유닛 파일이 기대한 위치에 없거나 이름이 달라진 경우입니다.

systemctl status my.service
systemctl cat my.service
systemctl list-unit-files | grep -E '^my\.service'
  • systemctl cat이 보여주는 경로가 중요합니다. 일반적으로는 아래 중 하나입니다.
    • /etc/systemd/system/my.service (로컬에서 만든 유닛)
    • /lib/systemd/system/my.service 또는 /usr/lib/systemd/system/my.service (패키지 제공)

유닛이 없다고 나오면:

sudo find /etc/systemd/system -maxdepth 2 -name '*my*'
sudo find /usr/lib/systemd/system -maxdepth 1 -name '*my*'

자주 놓치는 케이스

  • my.service가 아니라 my@.service 템플릿인데 인스턴스 이름이 빠짐: my@prod.service처럼 호출해야 함
  • 유닛 파일을 수정했는데 daemon-reload를 안 함
sudo systemctl daemon-reload

2분: 자동 시작 설정(Enable) 여부와 프리셋 확인

재부팅 후 자동으로 안 뜨는 경우, enable이 안 되어 있거나 preset 정책에 의해 disabled인 경우가 많습니다.

systemctl is-enabled my.service
systemctl list-dependencies --reverse multi-user.target | grep -F my.service
systemctl status my.service | sed -n '1,15p'
  • is-enabled 결과가 enabled가 아니면 부팅 시 올라오지 않을 수 있습니다.
sudo systemctl enable my.service
sudo systemctl enable --now my.service

enable인데도 부팅 후 안 뜨면

  • WantedBy가 엉뚱한 타겟을 바라보는지 확인해야 합니다.
systemctl cat my.service | sed -n '/\[Install\]/,$p'

예: 서버 서비스라면 보통 multi-user.target에 걸립니다.

3분: "부팅 때" 실패했는지, "지금" 실패했는지 분리

재부팅 직후 실패는 타이밍 이슈(네트워크, 마운트, DNS, 시크릿)일 확률이 큽니다. journalctl에서 이번 부팅의 로그만 보세요.

journalctl -u my.service -b --no-pager -n 200
journalctl -u my.service -b -o cat --no-pager | tail -n 80
  • -b는 현재 부팅 세션
  • 에러가 ExecStart의 표준 출력에만 찍히는 서비스도 많아서 -o cat이 유용합니다.

"재부팅 후"만 실패한다면, 다음 단계에서 의존성/순서/환경을 의심합니다.

4분: 실패 원인 1순위, ExecStart/WorkingDirectory/User 검증

systemd 실패의 절반은 ExecStart가 실제로 실행 불가능한 상태입니다.

systemctl show my.service -p ExecStart -p WorkingDirectory -p User -p Group -p Environment
systemctl show my.service -p FragmentPath -p UnitFileState -p ActiveState -p SubState

특히 확인할 것:

  • ExecStart 경로가 존재하는가
  • 실행 권한이 있는가
  • WorkingDirectory가 존재하는가
  • User로 실행할 때 접근 권한이 있는가

수동 재현(서비스 계정으로 실행):

sudo -u myuser /path/to/binary --version
sudo -u myuser /path/to/binary --config /etc/my/config.yml

흔한 함정: 상대 경로

유닛에서 ExecStart=./app 같은 상대 경로는 재부팅 후 작업 디렉터리가 달라져 바로 깨집니다. ExecStart절대 경로를 권장합니다.

5분: 부팅 시점 의존성(네트워크/마운트) 체크

서비스가 네트워크나 디스크 마운트에 의존한다면, 부팅 직후에는 준비가 안 되어 실패할 수 있습니다.

네트워크 준비

systemctl status network-online.target
systemctl list-dependencies my.service | grep -E 'network|remote|mount'

유닛에 아래 같은 의존성을 추가하는 패턴이 흔합니다.

[Unit]
Wants=network-online.target
After=network-online.target

마운트 준비

예: /data가 늦게 마운트되는데 서비스가 먼저 뜨면 실패합니다.

findmnt /data
systemctl status data.mount

유닛에:

[Unit]
RequiresMountsFor=/data
After=local-fs.target

6분: restart 정책과 StartLimit에 막혔는지 확인

부팅 시 여러 번 크래시하면 systemd가 "그만 재시도" 상태로 들어갈 수 있습니다.

systemctl status my.service
systemctl show my.service -p Restart -p RestartSec -p StartLimitIntervalUSec -p StartLimitBurst
journalctl -u my.service -b --no-pager | grep -E 'Start request repeated too quickly|start-limit'

막혀 있다면 리셋 후 재시도:

sudo systemctl reset-failed my.service
sudo systemctl start my.service

그리고 근본적으로는 크래시 원인을 잡아야 합니다. 메모리 부족으로 죽는 경우도 많으니, OOM 흔적도 같이 보세요.

7분: 환경 변수/시크릿/권한(특히 부팅 후만) 점검

재부팅 후만 실패한다면, 사람이 로그인한 쉘에서만 잡히는 환경변수에 의존했을 가능성이 큽니다. systemd는 로그인 쉘 환경을 자동으로 가져오지 않습니다.

EnvironmentFile 누락/권한

systemctl cat my.service | grep -E '^Environment|EnvironmentFile'
ls -al /etc/default/my
ls -al /etc/sysconfig/my
  • EnvironmentFile은 파일이 없으면 실패하게 만들 수도 있습니다(접두사 - 유무)
  • 파일 권한 때문에 User가 읽지 못할 수도 있습니다

권한/보안 옵션으로 막힘

ProtectSystem, ReadWritePaths, NoNewPrivileges 같은 하드닝 옵션이 재부팅 후 경로가 바뀌거나, 로그 디렉터리가 초기화되면서 실패를 유발할 수 있습니다.

systemctl show my.service -p ProtectSystem -p ReadWritePaths -p ReadOnlyPaths -p NoNewPrivileges

8분: “서비스는 떴는데 동작이 안 함”이면 포트/헬스/리버스프록시까지

Active: active (running)인데 외부에서 안 붙는 경우는 리스닝 포트, 바인딩 주소, 방화벽, 프록시 설정 문제일 확률이 큽니다.

sudo ss -lntp | grep -E ':80|:443|:8080|:3000'
curl -fsS http://127.0.0.1:PORT/health || echo 'health failed'

Nginx 뒤에 붙어 있고 재부팅 후만 이상하다면, 업스트림 준비 전에 프록시가 먼저 살아나서 헬스체크/리다이렉트가 꼬일 수도 있습니다.

9분: 재발 방지용 "진단 가능한" 유닛으로 정리

원인을 잡았으면, 다음 재부팅에서 같은 일이 생겨도 1분 만에 파악되도록 유닛을 정리합니다.

권장 유닛 템플릿(예시)

아래는 웹 API 바이너리를 systemd로 안정적으로 올리는 최소 예시입니다.

[Unit]
Description=My API Service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=myuser
Group=myuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=2

# 로그/진단 품질
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

# 파일 디스크립터/리소스
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

적용 순서:

sudo systemctl daemon-reload
sudo systemctl enable --now my.service
sudo systemctl status my.service

체크리스트 요약(프린트용)

  • 유닛 존재: systemctl status, systemctl cat
  • 자동 시작: systemctl is-enabled, WantedBy 확인
  • 부팅 로그: journalctl -u ... -b
  • 실행 경로/권한: ExecStart, WorkingDirectory, User
  • 의존성: network-online.target, RequiresMountsFor
  • 재시도 제한: StartLimit, reset-failed
  • 환경/시크릿: EnvironmentFile 존재/권한
  • 떠 있는데 안 됨: ss, curl localhost

마무리

재부팅 후 systemd 서비스가 안 뜨는 문제는 “재부팅” 자체보다 **부팅 시점의 조건(순서, 환경, 권한, 의존성)**이 다르기 때문에 발생합니다. 위 9분 루틴대로 보면 대부분은 journalctl에서 원인이 드러나고, 그 원인을 유닛의 의존성/경로/환경을 명시하는 방식으로 고칠 수 있습니다.

추가로, 재부팅 직후만 크래시하고 흔적이 애매하다면 OOM이나 리소스 제한도 꼭 같이 확인해 보세요.