RHEL 기초 #4 systemd 입문 — 서비스, target, journalctl
#3에서 패키지를 깔 줄 알게 됐습니다. 깐 것을 띄우고 / 멈추고 / 부팅 시 자동으로 올라오게 하고 / 로그를 보는 일은 모두 systemd 위에서 일어나요. RHEL 7부터의 표준이고, 우분투를 비롯한 거의 모든 모던 리눅스가 같은 모델을 씁니다.
RHEL 기초 시리즈에서 이번 글의 위치:
- #1 RHEL이란 — Fedora에서 RHEL까지, 그리고 AlmaLinux와 Rocky Linux
- #2 셋업 — RHEL 9 설치, Subscription Manager, 첫 로그인
- #3 dnf와 패키지 관리 — repo, modules, AppStream
- #4 systemd 입문 — 서비스, target, journalctl ← 이번 글
- #5 사용자/그룹/권한 — UID/GID, sudo, ACL
- #6 파일 시스템 기본 — XFS, mount, /etc/fstab
- #7 기본 보안 — firewalld, SSH 하드닝
systemd란 무엇인가 #
리눅스 부팅이 끝나면 사용자 영역에서 가장 먼저 뜨는 프로세스는 PID 1입니다. 옛날엔 init (System V init), 지금은 거의 모든 배포판에서 systemd입니다.
$ ps -p 1
PID TTY TIME CMD
1 ? 00:00:01 systemd
$ ls -l /sbin/init
lrwxrwxrwx. 1 root root ... /sbin/init -> ../lib/systemd/systemdsystemd는 단순한 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 |
이 글에서는 가장 자주 만나는 service와 target을 본격적으로 다루고, 나머지는 흐름만 짚어둡니다.
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 <서비스> 입니다.
시작 / 정지 / 재시작 #
$ sudo systemctl start nginx # 시작
$ sudo systemctl stop nginx # 정지
$ sudo systemctl restart nginx # 재시작
$ sudo systemctl reload nginx # 설정만 다시 읽기 (지원 시)restart와 reload는 다릅니다.
- restart — 프로세스를 죽이고 다시 띄움. 짧은 다운타임이 있음
- reload — 같은 프로세스가 SIGHUP 같은 신호를 받아 설정만 다시 읽음. 다운타임 없음. 모든 서비스가 지원하는 건 아님 (대표적으로 nginx, sshd는 됨)
운영에서 설정만 바꿨다면 reload가 안전하고, 메모리 상태도 함께 리셋해야 한다면 restart.
부팅 시 자동 시작 — enable / disable
#
$ sudo systemctl enable nginx # 부팅 시 자동 시작
$ sudo systemctl disable nginx # 자동 시작 끄기
$ sudo systemctl enable --now nginx # 즉시 시작 + 부팅 시 자동enable과 start는 다른 동작이라는 점을 외워두세요.
| 명령 | 지금 | 부팅 후 |
|---|---|---|
start | ✅ 시작 | ❌ 안 뜸 |
enable | ❌ 안 시작 | ✅ 자동 시작 |
enable --now | ✅ 시작 | ✅ 자동 시작 |
--now는 손이 자주 가는 옵션입니다. “방금 깐 서비스를 지금도 띄우고, 다음 부팅에도 뜨게” 가 거의 항상 원하는 동작입니다.
목록 보기 #
$ 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.target | 0 | 전원 차단 |
rescue.target | 1 | 단일 사용자 응급 모드 |
multi-user.target | 3 | 텍스트 멀티유저 (서버 기본) |
graphical.target | 5 | GUI까지 띄운 멀티유저 |
reboot.target | 6 | 재부팅 |
$ 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가 띄울 작은 스크립트를 만듭니다.
#!/bin/bash
while true; do
echo "[$(date +%H:%M:%S)] Hello from systemd"
sleep 5
done$ sudo chmod +x /usr/local/bin/hello-loop.shunit 파일 작성 #
[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] — 이 유닛 자체의 메타데이터.
Description—systemctl 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 파일을 만들어 줍니다.
$ 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.conf의 SystemMaxUse=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-reload | unit 파일 수정 후 다시 읽기 |
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-reload후enable --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/까지 한 번에 잡습니다.