리눅스 systemctl 명령어 사용법 - 서비스 관리와 유닛 파일 등록
이 명령어를 언제 쓰는가
서비스가 죽었다는 알림을 받았을 때, 설정을 고치고 반영해야 할 때, 서버를 재부팅해도 애플리케이션이 자동으로 뜨게 만들 때 쓴다. 현재 거의 모든 주류 배포판(Ubuntu 16.04+, Debian 8+, RHEL/CentOS 7+)이 systemd를 쓰므로 service와 chkconfig는 호환용 껍데기일 뿐이고 실제 동작은 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-pager | less로 넘기지 않고 그대로 출력(스크립트용) | 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-reload→restart순서다. start와enable은 완전히 다르다.start는 지금만 띄우고,enable은 부팅 시 자동 시작을 등록할 뿐 지금 띄우지는 않는다. 서버 점검 후 재부팅했더니 서비스가 안 올라오는 사고의 대부분은enable을 안 한 것이다. 둘 다 원하면enable --now를 쓴다.Type=을 잘못 쓰면 시작이 타임아웃으로 실패한다. 포그라운드로 도는 프로세스는Type=simple, 스스로 데몬화하며 부모가 종료되는 프로그램은Type=forking(대개PIDFile=도 필요)이다. 포크하는 프로그램에 simple을 주면 systemd가 프로세스를 놓치고, simple인 프로그램에 forking을 주면 시작이 끝나지 않는다.mask는disable보다 훨씬 강하다./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 공식 문서를 참고한다.