- Published on
systemd 서비스가 재부팅 후 안 뜰 때 9분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버 재부팅 후 “서비스가 안 떠요”는 흔하지만, 원인은 의외로 몇 가지 범주로 수렴합니다. 핵심은 부팅 시점에 systemd가 무엇을 시도했고, 왜 실패했는지를 짧은 시간 안에 재현 가능한 증거로 모으는 것입니다.
이 글은 9분 타임박스로 진단하는 흐름으로 구성했습니다. 각 단계는 명령 1~3개로 결론을 내리거나, 다음 단계로 넘길 근거를 만드는 데 집중합니다.
0분: 증상 분류부터 ("안 뜸"의 의미)
아래 중 무엇인지 먼저 분류하세요.
- 서비스 유닛이 아예 안 보임:
systemctl status my.service가Unit my.service could not be found비슷하게 나옴 - 유닛은 보이는데 inactive:
inactive (dead) - 유닛은 보이는데 failed:
failed (Result: exit-code) - 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이나 리소스 제한도 꼭 같이 확인해 보세요.