RHEL 기초 #4 systemd 입문 — 서비스, target, journalctl

9 분 소요

#3에서 패키지를 깔 줄 알게 됐습니다. 깐 것을 띄우고 / 멈추고 / 부팅 시 자동으로 올라오게 하고 / 로그를 보는 일은 모두 systemd 위에서 일어나요. RHEL 7부터의 표준이고, 우분투를 비롯한 거의 모든 모던 리눅스가 같은 모델을 씁니다.

RHEL 기초 시리즈에서 이번 글의 위치:

systemd란 무엇인가 #

리눅스 부팅이 끝나면 사용자 영역에서 가장 먼저 뜨는 프로세스는 PID 1입니다. 옛날엔 init (System V init), 지금은 거의 모든 배포판에서 systemd입니다.

PID 1 확인
$ ps -p 1
   PID TTY          TIME CMD
     1 ?        00:00:01 systemd

$ ls -l /sbin/init
lrwxrwxrwx. 1 root root ... /sbin/init -> ../lib/systemd/systemd

systemd는 단순한 init이 아니라 시스템 전체를 관리하는 매니저입니다. 하는 일을 한 줄씩 정리하면:

  • 부팅 순서를 의존성 그래프로 잡고 병렬로 띄우기
  • 서비스(데몬) 의 시작 / 정지 / 재시작 / 자동 복구
  • 마운트 / 스왑 / 타이머 / 소켓 같은 다른 자원도 같은 모델로 관리
  • 모든 자식 프로세스의 로그를 journald로 한 곳에 모음

옛 init은 /etc/init.d/<서비스> start 같은 셸 스크립트로 서비스를 띄웠습니다. 직렬로 동작해 느렸고, 디버깅도 어려웠습니다. systemd는 선언형입니다. “이 서비스가 어떤 모습으로 떠야 하는가"를 텍스트 파일 (unit)에 적어두면, 나머지는 systemd가 알아서 처리합니다.

unit의 종류 #

systemd가 다루는 기본 단위가 unit입니다. 여러 종류가 있습니다.

확장자의미
.service데몬 / 명령 (가장 자주)nginx.service, sshd.service
.socket소켓 활성화cockpit.socket
.target여러 unit의 묶음 (그룹)multi-user.target
.timer시간 기반 트리거 (cron 대체)dnf-makecache.timer
.mount파일시스템 마운트home.mount
.path파일 변화 감시cups.path
.slice / .scope자원 그룹 (cgroup)user-1000.slice

이 글에서는 가장 자주 만나는 servicetarget을 본격적으로 다루고, 나머지는 흐름만 짚어둡니다.

systemctl — systemd와 대화하는 명령 #

systemctl 한 명령이 거의 모든 systemd 작업을 합니다.

상태 보기 — status #

서비스 상태
$ systemctl status sshd
● sshd.service - OpenSSH server daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-04-12 10:01:32 KST; 1h ago
       Docs: man:sshd(8)
   Main PID: 942 (sshd)
      Tasks: 1 (limit: 4632)
     Memory: 5.2M
        CPU: 30ms
     CGroup: /system.slice/sshd.service
             └─942 "sshd: /usr/sbin/sshd -D ..."

Apr 12 10:01:32 rhel9-lab systemd[1]: Starting OpenSSH server daemon...
Apr 12 10:01:32 rhel9-lab sshd[942]: Server listening on 0.0.0.0 port 22.
Apr 12 10:01:32 rhel9-lab systemd[1]: Started OpenSSH server daemon.

status 한 번에 보이는 것들:

  • Loaded — unit 파일 경로, enabled (부팅 시 자동 시작) 여부
  • Active — 현재 동작 상태 (active (running), inactive (dead), failed…)
  • Main PID — 메인 프로세스의 PID
  • CGroup — 자식 프로세스가 어디 cgroup에 속해 있는지
  • 마지막 10 줄 정도의 로그 — 가장 최근 무슨 일이 있었는지

문제 진단의 첫 명령으로 거의 항상 systemctl status <서비스> 입니다.

시작 / 정지 / 재시작 #

lifecycle
$ sudo systemctl start nginx       # 시작
$ sudo systemctl stop nginx        # 정지
$ sudo systemctl restart nginx     # 재시작
$ sudo systemctl reload nginx      # 설정만 다시 읽기 (지원 시)

restartreload는 다릅니다.

  • restart — 프로세스를 죽이고 다시 띄움. 짧은 다운타임이 있음
  • reload — 같은 프로세스가 SIGHUP 같은 신호를 받아 설정만 다시 읽음. 다운타임 없음. 모든 서비스가 지원하는 건 아님 (대표적으로 nginx, sshd는 됨)

운영에서 설정만 바꿨다면 reload가 안전하고, 메모리 상태도 함께 리셋해야 한다면 restart.

부팅 시 자동 시작 — enable / disable #

enable / disable
$ sudo systemctl enable nginx     # 부팅 시 자동 시작
$ sudo systemctl disable nginx    # 자동 시작 끄기
$ sudo systemctl enable --now nginx   # 즉시 시작 + 부팅 시 자동

enablestart는 다른 동작이라는 점을 외워두세요.

명령지금부팅 후
start✅ 시작❌ 안 뜸
enable❌ 안 시작✅ 자동 시작
enable --now✅ 시작✅ 자동 시작

--now는 손이 자주 가는 옵션입니다. “방금 깐 서비스를 지금도 띄우고, 다음 부팅에도 뜨게” 가 거의 항상 원하는 동작입니다.

목록 보기 #

list-units / list-unit-files
$ systemctl list-units --type=service                  # 현재 메모리에 있는 서비스
$ systemctl list-unit-files --type=service             # 디스크에 있는 모든 service unit
$ systemctl list-units --state=failed                  # 실패한 서비스만

문제 추적 첫 단계로 --state=failed를 자주 봅니다. 부팅 직후 빨간 줄이 있나 확인.

target — 시스템의 “모드” #

옛 init의 runlevel(0~6)을 systemd는 target으로 대체했습니다. 한 모음의 유닛들을 묶은 일종의 “모드"입니다.

target옛 runlevel의미
poweroff.target0전원 차단
rescue.target1단일 사용자 응급 모드
multi-user.target3텍스트 멀티유저 (서버 기본)
graphical.target5GUI까지 띄운 멀티유저
reboot.target6재부팅
현재 target / 기본 target
$ systemctl get-default
graphical.target

$ systemctl is-active graphical.target
active

기본 target 바꾸기 #

GUI가 필요 없는 학습용 머신은 multi-user.target으로 부팅하면 메모리,CPU가 한결 가벼워집니다.

텍스트 모드로 부팅
$ sudo systemctl set-default multi-user.target
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.

$ sudo reboot

다시 GUI가 필요하면 set-default graphical.target. 재부팅 없이 즉시 모드 전환은:

즉시 전환 (한 번만)
$ sudo systemctl isolate multi-user.target     # 지금 텍스트 모드로
$ sudo systemctl isolate graphical.target      # 다시 GUI로

응급 / 복구 모드 #

부팅이 깨진 머신을 살릴 때 쓰는 모드들입니다. GRUB 부트 메뉴에서 진입.

target용도
rescue.target단일 사용자 모드. 거의 모든 서비스 안 뜬 상태 + root로 진입
emergency.target더 작은 모드. 파일시스템도 read-only로 마운트

GRUB 화면에서 e로 편집 후 커널 라인 끝에 systemd.unit=rescue.target을 붙이고 Ctrl+X로 부팅. 자세한 부팅 복구는 고급 #1에서 다룹니다.

.service 유닛 직접 만들기 #

남이 설치해 준 서비스만 다루는 건 절반의 systemd. 직접 unit을 한 장 적어보면 모델 전체가 머리에 들어옵니다.

토이 앱 한 장 #

먼저 systemd가 띄울 작은 스크립트를 만듭니다.

/usr/local/bin/hello-loop.sh
#!/bin/bash
while true; do
    echo "[$(date +%H:%M:%S)] Hello from systemd"
    sleep 5
done
실행 권한
$ sudo chmod +x /usr/local/bin/hello-loop.sh

unit 파일 작성 #

/etc/systemd/system/hello-loop.service
[Unit]
Description=Hello loop demo
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/hello-loop.sh
Restart=on-failure
RestartSec=2
User=nobody

[Install]
WantedBy=multi-user.target

세 섹션이 있습니다.

[Unit] — 이 유닛 자체의 메타데이터.

  • Descriptionsystemctl status에서 보일 한 줄 설명
  • After — 이 유닛이 시작되기 전에 떠 있어야 할 다른 유닛. 의존성. (강제 의존은 Requires)

[Service] — 어떻게 띄울지.

  • Type=simple — 가장 흔한 형. ExecStart로 띄운 프로세스가 메인 프로세스 (다른 형: forking, oneshot, notify…)
  • ExecStart — 띄울 명령. 절대 경로
  • Restart=on-failure — 비정상 종료 시 자동 재시작. (always / no / on-success 등도)
  • RestartSec — 재시작 간격 (초)
  • User=nobody — 어떤 사용자로 실행할지. 보안상 루트 외 사용자로 떨어트리는 게 기본

[Install]enable 했을 때의 동작.

  • WantedBy=multi-user.target — 이 target이 활성화될 때 같이 시작. 이게 없으면 enable이 동작하지 않습니다.

활성화 #

등록 / 시작 / 확인
$ sudo systemctl daemon-reload                # 새 unit 파일을 systemd 가 읽게
$ sudo systemctl enable --now hello-loop
Created symlink ...

$ systemctl status hello-loop
● hello-loop.service - Hello loop demo
     Loaded: loaded (/etc/systemd/system/hello-loop.service; enabled; ...)
     Active: active (running) since ...
   Main PID: 12345 (hello-loop.sh)
     ...
     CGroup: /system.slice/hello-loop.service
             ├─12345 /bin/bash /usr/local/bin/hello-loop.sh
             └─12389 sleep 5

함정daemon-reload를 빼먹는 일이 가장 흔합니다. unit 파일을 새로 만들거나 수정한 뒤에는 항상 daemon-reload — 안 그러면 systemd가 이전 정의로 동작합니다.

unit 파일을 어디 두느냐도 중요합니다.

경로용도
/usr/lib/systemd/system/패키지가 설치한 unit (직접 수정 금지)
/etc/systemd/system/사용자가 만든 / 덮어쓰는 unit
~/.config/systemd/user/사용자 단위 (user systemd)

같은 이름이 둘 다 있으면 /etc/systemd/system/이 우선 합니다. 패키지 unit의 일부만 바꾸고 싶으면 systemctl edit <서비스> — drop-in 디렉터리에 override 파일을 만들어 줍니다.

drop-in override
$ sudo systemctl edit nginx
# 에디터가 떠서 /etc/systemd/system/nginx.service.d/override.conf 를 편집
[Service]
Environment="NGINX_OPTS=-q"
LimitNOFILE=65536

원본을 안 건드리면서 설정만 덮을 수 있습니다. 패키지 업데이트 시 원본이 갱신돼도 override는 살아남습니다.

journalctl — 모든 로그가 모이는 곳 #

systemd 에는 journald 라는 로그 데몬이 같이 들어 있습니다. 모든 unit의 stdout/stderr, 커널 메시지, syslog까지 한 곳에 모여요.

기본
$ journalctl                           # 전체 (Page Down으로 스크롤)
$ journalctl -e                        # 끝(가장 최근) 으로
$ journalctl -f                        # 실시간 follow (tail -f 처럼)
$ journalctl -n 100                    # 최근 100 줄만

필터 #

유닛 / 부팅 / 시간 / 우선순위
$ journalctl -u nginx                  # nginx unit 의 로그만
$ journalctl -u nginx -f               # nginx 실시간

$ journalctl -b                        # 이번 부팅의 로그만
$ journalctl -b -1                     # 직전 부팅
$ journalctl --list-boots              # 보관된 부팅 목록

$ journalctl --since "2026-04-12 10:00" --until "2026-04-12 11:00"
$ journalctl --since "1 hour ago"
$ journalctl --since today

$ journalctl -p err                    # err 이상의 우선순위
$ journalctl -p warning..err           # 범위

자주 쓰는 조합 #

실전
$ journalctl -u sshd -f                # sshd 만 실시간 보기
$ journalctl -u nginx --since today -p err  # 오늘 에러만
$ journalctl _PID=12345                # 특정 PID 의 로그
$ journalctl _COMM=sudo                # sudo 명령 자체의 로그

filter의 _PID, _COMM, _UID 같은 키는 journald가 자동으로 채우는 메타데이터입니다. journalctl -F _COMM으로 어떤 값들이 들어 있는지 둘러볼 수 있습니다.

journald의 보관 #

기본은 휘발성 — 재부팅하면 이전 로그가 사라져요. 영구 보관하려면 한 줄을 켭니다.

영구 보관
$ sudo mkdir -p /var/log/journal
$ sudo systemd-tmpfiles --create --prefix /var/log/journal
$ sudo systemctl restart systemd-journald

이후 journalctl --list-boots가 여러 부팅을 보여주면 영구 보관이 켜진 것. 디스크 사용량 제한은 /etc/systemd/journald.confSystemMaxUse=1G 같은 값으로.

/etc/init.d/는 어디로 갔나 #

옛 자료를 보면 service nginx start/etc/init.d/nginx start 같은 표현이 자주 나옵니다. RHEL 9에서 둘 다 동작은 하지만, 안쪽에서 systemctl로 변환됩니다.

옛 명령 (호환용)
$ sudo service nginx status        # systemctl status nginx로 변환됨
$ sudo chkconfig nginx on          # systemctl enable nginx로 변환됨

새로 익히는 분은 처음부터 systemctl로 가는 게 맞습니다. 옛 명령은 옛 자료를 읽을 때 알아두는 정도.

자주 쓰는 명령 한 표 #

명령하는 일
systemctl status <unit>상태 + 최근 로그
systemctl start/stop/restart/reload <unit>lifecycle
systemctl enable [--now] <unit>부팅 시 자동 (+ 즉시 시작)
systemctl disable [--now] <unit>자동 시작 해제 (+ 즉시 정지)
systemctl is-active/is-enabled <unit>한 단어로 답
systemctl list-units --state=failed실패한 unit만
systemctl daemon-reloadunit 파일 수정 후 다시 읽기
systemctl get-default / set-default기본 target 확인 / 변경
systemctl isolate <target>지금 즉시 target 전환
systemctl edit <unit>drop-in override 편집
journalctl -u <unit> [-f]그 unit의 로그 (+ 실시간)
journalctl -b [-1]이번/직전 부팅 로그
journalctl --since "..." --until "..."시간 범위
journalctl -p err우선순위 필터

자주 만나는 함정 #

“유닛이 안 보입니다” #

새 unit 파일을 만들었는데 systemctl start가 “Unit not found” 면 거의 항상 daemon-reload 누락. unit 파일을 디스크에 두는 것만으로 systemd가 자동 인식하진 않습니다.

“enable 했는데 부팅 후 안 떠요” #

unit 파일에 [Install] 섹션이 빠진 경우. WantedBy=multi-user.target 한 줄이 있어야 enable이 의미를 가집니다. systemctl enable이 “no installation config” 류 메시지를 띄우면 이 문제입니다.

“Active: failed” #

뭐가 잘못됐는지 가장 빠르게 확인하려면 journalctl -u <unit> -e. 시작 직후 어떤 에러로 죽었는지 한 화면에 보여줍니다.

흔한 원인:

  • ExecStart의 명령이 상대 경로 (절대 경로여야 함)
  • 실행 파일에 실행 권한 없음 (chmod +x)
  • User=로 지정한 사용자가 파일에 접근 못 함 (권한)
  • Type=가 실제 동작과 안 맞음

“로그가 너무 많습니다” #

journalctl만 치면 부팅 이후 모든 게 나옵니다. 거의 항상 -u <unit>, -b, --since 중 하나는 붙여 좁혀요.

정리 #

이번 글에서 잡은 그림:

  • systemd가 RHEL의 PID 1 — 모든 부팅/서비스를 잡고 있는 매니저
  • unit은 systemd가 다루는 단위. service, target, timer, mount 등 여러 종류
  • systemctl status / start / stop / restart / reload / enable / disable가 일상 명령. **enable --now**가 손에 가장 자주
  • target은 옛 runlevel의 대체. multi-user.target (텍스트) / graphical.target (GUI) / rescue.target (응급)
  • 직접 작성한 .service 유닛은 /etc/systemd/system/에 두고 daemon-reloadenable --now
  • systemctl edit으로 패키지 unit을 안 건드리고 drop-in override
  • journalctl이 모든 로그가 모이는 곳 — -u, -b, --since, -p, -f의 조합으로 좁혀 본다

다음 — 사용자와 권한 #

systemd가 서비스를 어떤 사용자로 띄울지는 unit의 User= 한 줄로 정해져요. 그 사용자가 시스템에 어떻게 존재하고, 어떤 파일에 접근할 수 있는지의 모델이 다음 글의 주제입니다.

#5 사용자/그룹/권한 — UID/GID, sudo, ACL 에서는 /etc/passwd/etc/shadow의 모양, useradd / usermod / groupadd 명령군, 파일 권한 rwx의 의미와 chmod의 두 표기법, 더 세밀한 권한이 필요할 때 쓰는 ACL (getfacl/setfacl), 그리고 sudo/etc/sudoers.d/까지 한 번에 잡습니다.

X