Devin.KR
로그인

리눅스 systemctl 명령어 사용법 - 서비스 관리와 유닛 파일 등록

개발자 조회 2

이 명령어를 언제 쓰는가

서비스가 죽었다는 알림을 받았을 때, 설정을 고치고 반영해야 할 때, 서버를 재부팅해도 애플리케이션이 자동으로 뜨게 만들 때 쓴다. 현재 거의 모든 주류 배포판(Ubuntu 16.04+, Debian 8+, RHEL/CentOS 7+)이 systemd를 쓰므로 servicechkconfig는 호환용 껍데기일 뿐이고 실제 동작은 systemctl이 한다.

서비스 상태 확인 → 로그 확인 → 설정 수정 → 재적용, 이 네 단계가 운영 업무의 대부분이다. systemctl은 그중 첫째와 넷째를 담당한다.

기본 형식

systemctl [옵션] 동작 [유닛명]
  • 동작status, start, stop, restart, enable 등.
  • 유닛명nginx.service처럼 쓰지만 .service는 생략할 수 있다. 타이머는 .timer, 마운트는 .mount처럼 확장자를 붙여야 한다.
  • 상태 조회는 일반 사용자도 가능하지만, 시작·정지·활성화는 root 권한이 필요하다.

자주 쓰는 동작과 옵션

동작 / 옵션의미예시
status현재 상태·최근 로그 10줄·메인 PID 확인systemctl status nginx
start / stop지금 시작 / 정지 (재부팅과 무관)sudo systemctl stop nginx
restart정지 후 시작. 연결이 끊긴다sudo systemctl restart myapp
reload프로세스를 죽이지 않고 설정만 다시 읽음sudo systemctl reload nginx
enable / disable부팅 시 자동 시작 등록 / 해제sudo systemctl enable nginx
enable --now자동 시작 등록과 즉시 시작을 한 번에sudo systemctl enable --now myapp
is-active / is-enabled스크립트에서 쓰기 좋은 단답 확인(종료코드 반환)systemctl is-active nginx
daemon-reload유닛 파일 변경을 systemd에 인식시킴sudo systemctl daemon-reload
list-units --failed실패한 유닛만 모아 보기systemctl list-units --failed
cat실제로 적용 중인 유닛 파일 내용 출력systemctl cat myapp
edit원본을 건드리지 않고 오버라이드 파일 작성sudo systemctl edit myapp
mask / unmask어떤 방법으로도 시작되지 않게 완전 차단 / 해제sudo systemctl mask apache2
--no-pagerless로 넘기지 않고 그대로 출력(스크립트용)systemctl status nginx --no-pager

실전 예제

1. 서비스가 죽었을 때 상태부터 읽는다. 여기서 대부분의 원인이 드러난다.

$ systemctl status myapp --no-pager
● myapp.service - My Application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Tue 2026-08-25 11:20:03 KST; 2min ago
    Process: 4812 ExecStart=/usr/local/bin/myapp (code=exited, status=1/FAILURE)
   Main PID: 4812 (code=exited, status=1/FAILURE)

Loaded 줄의 enabled/disabled가 부팅 자동 시작 여부, Active 줄이 지금 상태다. status=1/FAILURE가 보이면 애플리케이션 자체가 종료된 것이므로 다음은 로그다.

sudo journalctl -u myapp -n 50 --no-pager

2. nginx 설정을 고친 뒤 무중단으로 반영한다. restart가 아니라 reload를 쓰는 이유는 기존 연결을 끊지 않기 때문이다. 반영 전 문법 검사는 필수다.

sudo nginx -t
sudo systemctl reload nginx

nginx -t가 실패한 상태로 restart를 하면 서비스가 아예 안 올라와 장애가 된다. reload를 지원하지 않는 서비스는 systemctl show -p CanReload myapp로 확인할 수 있다.

3. 직접 만든 애플리케이션을 서비스로 등록한다. nohup으로 띄우고 잊어버리는 대신 이렇게 한다.

sudo tee /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
WorkingDirectory=/srv/myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/app.yml
Restart=on-failure
RestartSec=5
UMask=0027

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp --no-pager

4. 서버에 문제가 있는지 전체를 훑는다. 장애 대응 초반에 치는 명령이다.

systemctl list-units --failed
systemctl list-units --type=service --state=running
systemd-analyze blame | head -10

마지막 줄은 부팅이 느릴 때 어떤 유닛이 시간을 먹었는지 보여준다.

5. 패키지가 제공한 유닛 파일을 건드리지 않고 일부만 바꾼다. 패키지 업데이트 때 덮어써지는 사고를 피하는 방법이다.

sudo systemctl edit myapp

열린 편집기에 바꿀 항목만 적는다. 저장하면 /etc/systemd/system/myapp.service.d/override.conf로 저장된다.

[Service]
Environment=LOG_LEVEL=debug
LimitNOFILE=65535
sudo systemctl daemon-reload && sudo systemctl restart myapp
systemctl cat myapp        # 원본 + 오버라이드가 합쳐진 최종 형태 확인

6. 배포 스크립트에서 서비스가 실제로 살아났는지 검증한다.

sudo systemctl restart myapp
if systemctl is-active --quiet myapp; then
  echo "restart ok"
else
  sudo journalctl -u myapp -n 30 --no-pager
  exit 1
fi

함정과 주의점

  • 유닛 파일을 고치고 daemon-reload를 빼먹으면 예전 설정으로 돈다. restart만 하면 systemd는 메모리에 캐시된 옛 유닛 정의를 그대로 쓴다. "분명히 고쳤는데 반영이 안 된다"의 1순위 원인이다. 유닛 파일을 만졌으면 daemon-reloadrestart 순서다.
  • startenable은 완전히 다르다. start는 지금만 띄우고, enable은 부팅 시 자동 시작을 등록할 뿐 지금 띄우지는 않는다. 서버 점검 후 재부팅했더니 서비스가 안 올라오는 사고의 대부분은 enable을 안 한 것이다. 둘 다 원하면 enable --now를 쓴다.
  • Type=을 잘못 쓰면 시작이 타임아웃으로 실패한다. 포그라운드로 도는 프로세스는 Type=simple, 스스로 데몬화하며 부모가 종료되는 프로그램은 Type=forking(대개 PIDFile=도 필요)이다. 포크하는 프로그램에 simple을 주면 systemd가 프로세스를 놓치고, simple인 프로그램에 forking을 주면 시작이 끝나지 않는다.
  • maskdisable보다 훨씬 강하다. /dev/null로 심볼릭 링크를 걸어 다른 유닛의 의존성으로도 시작되지 않게 한다. mask된 서비스는 start를 해도 조용히 실패하므로, 시작이 안 되는데 원인을 못 찾겠다면 systemctl is-enabled 유닛masked인지 확인한다.
  • 서비스를 stop한 뒤 프로세스를 직접 띄우지 않는다. 손으로 띄운 프로세스는 systemd가 관리하지 않아 status에 안 잡히고, 나중에 누군가 restart를 하면 포트 충돌로 이중 기동 사고가 난다.
  • Restart=always는 만능이 아니다. 설정 오류로 즉시 죽는 프로세스에 걸면 무한 재시작 루프에 빠지고, systemd의 시작 제한(StartLimitBurst)에 걸려 결국 failed로 멈춘다. 이 경우 systemctl reset-failed myapp 후 원인을 고쳐야 한다.

함께 보면 좋은 명령어

  • journalctl -u 유닛 — systemctl status가 보여주는 10줄로는 부족할 때 전체 로그를 본다.
  • systemd-analyze — 부팅 시간과 유닛 의존성을 분석한다.
  • ss -tlnp — 서비스가 실제로 포트를 열었는지 확인한다.
  • systemctl list-timers — cron 대신 systemd 타이머를 쓰는 환경에서 예약 작업을 확인한다.

유닛 파일 항목의 정확한 의미는 systemd.service 공식 문서를 참고한다.