서비스 · 프로세스 관리 23 / 50 · Part 3. systemd와 서비스
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

systemctl start, enable, restart, reload는 매일 쓰는 명령이지만, 각각이 정확히 무엇을 바꾸는지 는 헷갈리기 쉽다. "enable 했는데 서비스가 안 떠요", "restart와 reload의 차이가 뭔가요", "서비스를 확실히 못 뜨게 하려면?" 같은 질문이 모두 여기서 나온다.

이번 글에서는 systemctl 핵심 명령을 런타임(지금 실행 상태) 과 설치 상태(부팅 시 동작) 두 축으로 나누어, 각 명령 전후의 프로세스와 파일 시스템 변화를 직접 확인한다.

「리눅스 시스템 기초 37편」에서 systemctl의 기본 사용법을 다뤘다. 이번 글은 각 명령의 내부 동작 확인에 집중한다.


2. 핵심 개념

2-1. 런타임 명령

명령동작PID
start서비스 시작 (의존성 포함)새로 생성
stop종료 절차 (ExecStop → TERM → KILL, 13편)사라짐
restartstop 후 start바뀜
reloadExecReload= 실행, 설정만 다시 읽음유지
reload-or-restartreload 지원 시 reload, 아니면 restart—
try-restart실행 중일 때만 restart—
kill -s SIG서비스 cgroup 프로세스에 시그널—

2-2. 설치 상태 명령

명령동작
enable[Install]에 따라 *.wants/ 링크 생성 → 부팅 시 시작
disable링크 제거
enable --now / disable --now링크 변경 + 즉시 start / stop
maskunit을 /dev/null로 링크 → 어떤 방법으로도 시작 불가
unmaskmask 해제

2-3. 조회 명령

명령출력종료 코드
is-active UNITactive / inactive / failedactive면 0
is-enabled UNITenabled / disabled / static / maskedenabled면 0
is-failed UNITfailed 여부failed면 0
status UNIT상태 요약 + 최근 로그24편
show UNIT -p PROP속성 값 (스크립트용)—
list-dependencies UNIT의존성 트리26편

3. 동작 원리

systemctl 명령이 실제로 바꾸는 것

런타임과 설치 상태는 서로 독립 이다. 그래서 네 가지 조합이 모두 가능하다.

systemctl start atd     → 지금 실행 (재부팅하면? 설치 상태에 따름)
systemctl enable atd    → 부팅 시 실행 (지금은? 그대로)
systemctl enable --now  → 둘 다
systemctl mask atd      → /etc/systemd/system/atd.service → /dev/null
                          /etc 가 /usr/lib 보다 우선(22편)이라 원본 대신 빈 파일이 선택됨 → 시작 불가

reload는 서비스가 ExecReload=를 정의했을 때만 가능하다. 대부분 11편의 SIGHUP을 보내는 방식 으로 구현되어 있어, 접속을 끊지 않고 설정만 다시 읽는다.


4. 명령어 실습

# 1) 런타임: stop · start · restart
systemctl stop atd; systemctl is-active atd; pgrep -a atd || echo "atd 프로세스 없음"
systemctl start atd; systemctl is-active atd; pgrep -a atd
OLD=$(systemctl show atd -p MainPID --value); systemctl restart atd
NEW=$(systemctl show atd -p MainPID --value); echo "restart: PID $OLD → $NEW"
systemctl status atd --no-pager | head -8

# 2) 설치 상태: disable · enable · mask
systemctl is-enabled atd; systemctl disable atd; systemctl is-enabled atd
systemctl enable atd; ls -l /etc/systemd/system/multi-user.target.wants/atd.service
systemctl mask atd; ls -l /etc/systemd/system/atd.service
systemctl start atd; systemctl is-enabled atd
systemctl unmask atd; systemctl start atd; systemctl is-active atd

# 3) (Ubuntu) reload, 의존성, 여러 unit 조회
systemctl show ssh -p CanReload,ExecReload --no-pager
systemctl reload ssh && journalctl -u ssh -n 2 --no-pager -o cat
systemctl list-dependencies cron --no-pager | head -8
systemctl is-active cron nonexistent.service; echo "종료 코드=$?"

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — start · stop · restart · status

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — enable · disable · mask

실제 실행 결과 — Ubuntu 24.04.5 · root@ubuntu-lab — reload · 의존성 · 관리 명령 요약

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# systemctl stop atd; systemctl is-active atd; pgrep -a atd || echo "atd 프로세스 없음"
inactive
atd 프로세스 없음
[root@rocky9-lab ~]# systemctl start atd; systemctl is-active atd; pgrep -a atd
active
2631 /usr/sbin/atd -f
[root@rocky9-lab ~]# OLD=$(systemctl show atd -p MainPID --value); systemctl restart atd; NEW=$(systemctl show atd -p MainPID --value); echo "restart: PID $OLD → $NEW"
restart: PID 2631 → 2637
[root@rocky9-lab ~]# systemctl status atd --no-pager | head -8
● atd.service - Deferred execution scheduler
     Loaded: loaded (/usr/lib/systemd/system/atd.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-24 12:10:32 UTC; 20ms ago
       Docs: man:atd(8)
   Main PID: 2637 (atd)
      Tasks: 1 (limit: 51293)
     Memory: 288.0K
     CGroup: /docker/5a46f907edb23397a00c52e8214c9c387f1cf283dd50c13b81f6c01a58252f3b/system.slice/atd.service
[root@rocky9-lab ~]# systemctl is-enabled atd; systemctl disable atd; systemctl is-enabled atd
enabled
Removed "/etc/systemd/system/multi-user.target.wants/atd.service".
disabled
[root@rocky9-lab ~]# ls -l /etc/systemd/system/multi-user.target.wants/ | grep -c atd
0
[root@rocky9-lab ~]# systemctl enable atd; ls -l /etc/systemd/system/multi-user.target.wants/atd.service
Created symlink /etc/systemd/system/multi-user.target.wants/atd.service → /usr/lib/systemd/system/atd.service.
lrwxrwxrwx 1 root root 35 Sep 24 12:10 /etc/systemd/system/multi-user.target.wants/atd.service -> /usr/lib/systemd/system/atd.service
[root@rocky9-lab ~]# systemctl mask atd; ls -l /etc/systemd/system/atd.service
Created symlink /etc/systemd/system/atd.service → /dev/null.
lrwxrwxrwx 1 root root 9 Sep 24 12:10 /etc/systemd/system/atd.service -> /dev/null
[root@rocky9-lab ~]# systemctl start atd; systemctl is-enabled atd
Failed to start atd.service: Unit atd.service is masked.
masked
[root@rocky9-lab ~]# systemctl unmask atd; systemctl start atd; systemctl is-active atd
Removed "/etc/systemd/system/atd.service".
active
root@ubuntu-lab:~# systemctl show ssh -p CanReload,ExecReload --no-pager
ExecReload={ path=/usr/sbin/sshd ; argv[]=/usr/sbin/sshd -t ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
ExecReload={ path=/bin/kill ; argv[]=/bin/kill -HUP $MAINPID ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
CanReload=yes
root@ubuntu-lab:~# systemctl reload ssh && journalctl -u ssh -n 2 --no-pager -o cat
Reloaded ssh.service - OpenBSD Secure Shell server.
Received SIGHUP; restarting.
root@ubuntu-lab:~# systemctl list-dependencies cron --no-pager | head -8
cron.service
● ├─system.slice
● └─sysinit.target
●   ├─dev-hugepages.mount
●   ├─dev-mqueue.mount
○   ├─kmod-static-nodes.service
○   ├─ldconfig.service
●   ├─proc-sys-fs-binfmt_misc.automount
root@ubuntu-lab:~# systemctl is-active cron nonexistent.service; echo "종료 코드=$?"
active
inactive
종료 코드=0

6. 결과 해석

관찰의미
stop 후 inactive, 프로세스 없음런타임 상태만 바뀌었다. enable 상태는 그대로다
start 후 2631 /usr/sbin/atd -fPID 1이 새 프로세스를 만들었다
restart: PID 2631 → 2637restart는 프로세스를 새로 만든다. 메모리 상태·연결이 모두 초기화된다
status의 Loaded: ... enabled; preset: enabled, Active: active (running)한 화면에 설치 상태와 런타임 상태가 함께 보인다 (24편)
disable → Removed ".../multi-user.target.wants/atd.service"disable = 링크 삭제 다. 프로세스는 계속 실행 중이다
enable → Created symlink ... → /usr/lib/systemd/system/atd.serviceenable = 링크 생성
mask → atd.service → /dev/null/etc에 빈 파일 링크를 만들어 원본을 가렸다
mask 상태에서 start → Unit atd.service is masked.수동 시작도 거부된다. 다른 unit의 의존성으로도 시작되지 않는다
Ubuntu ExecReload 두 개: sshd -t → kill -HUP $MAINPIDreload 전에 설정 문법을 검사 하고, 통과해야 HUP을 보낸다. 잘못된 설정으로 sshd가 죽는 것을 막는 안전장치다
로그 Received SIGHUP; restarting.sshd가 HUP을 받아 설정을 다시 읽었다. 기존 SSH 세션은 유지된다
is-active cron nonexistent.service → active / inactive, 종료 코드 0여러 unit을 주면 하나라도 active면 0 을 반환한다. 스크립트에서 한 번에 여러 개를 검사하면 오판할 수 있다

7. 보안 관점

주제내용
mask로 공격 표면 축소쓰지 않는 서비스(예: rpcbind, cups, avahi-daemon)는 disable만으로는 다른 unit의 의존성으로 켜질 수 있다. mask 가 확실하다
보안 서비스 mask 공격반대로 공격자가 auditd, EDR 에이전트를 mask하면 재부팅 후에도 켜지지 않는다. /etc/systemd/system/*.service → /dev/null 링크를 점검한다
disabled + active 함정조치 후 stop만 하고 disable하지 않은 악성 unit은 재부팅 시 되살아난다. 반대로 disable만 하면 지금은 계속 돈다
sudo 위임sudo systemctl restart nginx처럼 명령과 대상 unit을 한정 해 위임한다. systemctl 전체 위임은 root 위임과 같다

8. 보안관제 관점

[Detection]  EDR 하트비트 끊김
     ↓
[확인]       systemctl is-active edr-agent ; systemctl is-enabled edr-agent
             → inactive / masked
     ↓
[증거]       ls -l --time-style=full-iso /etc/systemd/system/edr-agent.service   ← mask 링크 생성 시각
             journalctl --since "<그 시각 -5min>" -u edr-agent
             journalctl _COMM=sudo --since "<그 시각 -5min>"
     ↓
[Response]   systemctl unmask --now ... 복구 → mask 한 계정·세션 조사
점검 명령목적
systemctl list-unit-files --state=maskedmask된 unit 목록 (보안 서비스 포함 여부)
systemctl list-unit-files --state=enabled부팅 시 시작 목록 (기준선 비교)
journalctl -g 'Created symlink\|Removed'enable/disable/mask 흔적 (systemctl 출력이 로그에 남은 경우)

9. 실무에서 자주 발생하는 실수

실수결과예방
enable만 하고 start 안 함재부팅 전까지 서비스 없음enable --now
start만 하고 enable 안 함재부팅 후 서비스 사라짐운영 서비스는 enable 확인
설정 변경에 무조건 restart연결 끊김·세션 초기화지원하면 reload
서비스 차단에 disable만 사용의존성으로 다시 시작mask
여러 unit을 is-active 한 번으로 검사하나만 active여도 성공unit별로 개별 검사

10. 실습 체크리스트

[ ] stop/start 후 프로세스 유무를 확인했다
[ ] restart 전후 MainPID 가 바뀌는 것을 확인했다
[ ] disable/enable 이 *.wants 링크 삭제/생성인 것을 확인했다
[ ] mask 가 /dev/null 링크이고 start 가 거부되는 것을 확인했다
[ ] ExecReload 와 reload 시 SIGHUP 로그를 확인했다
[ ] is-active 에 여러 unit 을 줄 때의 종료 코드 함정을 확인했다

11. 핵심 정리

  • start/stop/restart/reload는 지금 을, enable/disable/mask는 부팅 시 를 바꾼다. 둘은 독립이다.
  • restart는 PID가 바뀌고, reload는 PID를 유지한 채 설정만 다시 읽는다.
  • enable/disable은 *.wants/ 링크 조작, mask는 /dev/null 링크로 시작 자체를 막는다.
  • 불필요한 서비스는 mask로 차단하고, 보안 서비스의 mask 여부는 정기 점검한다.
  • 스크립트에서 is-active는 unit별로 검사한다.

12. 다음 편 예고

다음 글 「24. 서비스 상태 해석 (active·enabled·failed)」 에서는 LoadState, ActiveState, SubState, UnitFileState 네 가지 값으로 서비스 상태를 정확히 읽는 법을 다룬다. 일부러 실패하는 서비스를 만들어 status=203/EXEC 같은 실패 코드를 해석 한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글