systemd란?
systemd는 대부분의 최신 Linux 배포판(Ubuntu, CentOS, Fedora, Debian 등)에서 사용하는 시스템 및 서비스 매니저입니다. 부팅 시 프로세스를 시작하고, 서비스의 생명주기를 관리하며, 로그를 수집합니다. 기존 SysVinit의 순차 부팅 방식을 대체하여 병렬 부팅을 지원하므로 부팅 속도가 빠릅니다.
systemd의 핵심 구성 요소는 다음과 같습니다.
| 구성 요소 | 역할 |
|---|---|
| systemctl | 서비스 제어 CLI 도구 |
| journalctl | 로그 조회 도구 |
| unit 파일 | 서비스 정의 파일 (.service, .timer 등) |
| target | 서비스 그룹 (런레벨 대체) |
서비스 상태 관리
systemctl 명령어로 서비스를 시작, 중지, 재시작하고 상태를 확인합니다.
# 서비스 상태 확인
sudo systemctl status nginx
# ● nginx.service - A high performance web server
# Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
# Active: active (running) since Mon 2026-02-19 09:00:00 KST
# Main PID: 1234 (nginx)
# 서비스 시작 / 중지 / 재시작
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
# 설정 변경 후 프로세스 재시작 없이 리로드
sudo systemctl reload nginx
# 부팅 시 자동 시작 등록 / 해제
sudo systemctl enable nginx
sudo systemctl disable nginx
# 서비스를 활성화하면서 즉시 시작
sudo systemctl enable --now nginx
enable은 부팅 시 자동 시작을 등록하는 것이고, start는 지금 즉시 시작하는 것입니다. 신규 서비스를 등록할 때는 enable --now를 사용하면 두 명령을 한 번에 처리할 수 있습니다.
커스텀 서비스 파일 작성
Node.js 애플리케이션을 systemd 서비스로 등록하는 예제입니다. 서비스 파일은 /etc/systemd/system/ 디렉토리에 .service 확장자로 생성합니다.
# /etc/systemd/system/my-app.service
[Unit]
# 서비스 설명
Description=My Node.js Application
# 네트워크가 올라온 후 시작
After=network.target
# PostgreSQL 서비스가 먼저 시작된 후 실행
Wants=postgresql.service
[Service]
# 서비스 타입 (simple: 프로세스가 곧 서비스)
Type=simple
# 실행 사용자/그룹
User=deploy
Group=deploy
# 작업 디렉토리
WorkingDirectory=/opt/my-app
# 환경변수 파일 로드
EnvironmentFile=/opt/my-app/.env
# 실행 명령어
ExecStart=/usr/bin/node /opt/my-app/dist/server.js
# 재시작 정책 (비정상 종료 시 재시작)
Restart=on-failure
# 재시작 대기 시간 (초)
RestartSec=5
# 최대 파일 디스크립터 수
LimitNOFILE=65535
# 표준 출력/에러를 journal로 전송
StandardOutput=journal
StandardError=journal
# journal에서 식별할 태그
SyslogIdentifier=my-app
[Install]
# multi-user.target에 포함 (일반 부팅 시 시작)
WantedBy=multi-user.target
서비스 파일을 작성한 후 다음 순서로 등록합니다.
# 서비스 파일 변경 사항 반영
sudo systemctl daemon-reload
# 서비스 활성화 + 즉시 시작
sudo systemctl enable --now my-app.service
# 상태 확인
sudo systemctl status my-app.service
# ● my-app.service - My Node.js Application
# Loaded: loaded (/etc/systemd/system/my-app.service; enabled)
# Active: active (running) since Mon 2026-02-19 09:10:00 KST
# Main PID: 5678 (node)
# 서비스 파일 문법 검증
sudo systemd-analyze verify /etc/systemd/system/my-app.service
daemon-reload는 서비스 파일을 수정할 때마다 반드시 실행해야 합니다. 이 명령 없이는 변경 사항이 반영되지 않습니다.
Service 섹션 주요 옵션
서비스의 동작 방식을 세밀하게 제어하는 옵션들입니다.
| 옵션 | 값 | 설명 |
|---|---|---|
| Type | simple | 프로세스 자체가 서비스 (기본값) |
| Type | forking | 데몬 방식 (부모 프로세스가 fork 후 종료) |
| Type | oneshot | 한 번 실행 후 종료 (스크립트 등) |
| Type | notify | 서비스가 준비 완료를 systemd에 알림 |
| Restart | no | 재시작 안 함 (기본값) |
| Restart | on-failure | 비정상 종료 시만 재시작 |
| Restart | always | 어떤 이유로든 재시작 |
| RestartSec | 초 | 재시작 전 대기 시간 |
| TimeoutStartSec | 초 | 시작 타임아웃 |
| TimeoutStopSec | 초 | 종료 타임아웃 |
journalctl 로그 확인
systemd는 모든 서비스 로그를 journal에 통합 저장합니다. journalctl로 조회합니다.
# 특정 서비스 로그 (최신 50줄)
sudo journalctl -u my-app.service -n 50
# 실시간 로그 스트리밍 (tail -f 처럼)
sudo journalctl -u my-app.service -f
# 오늘 로그만 보기
sudo journalctl -u my-app.service --since today
# 특정 시간 범위 로그
sudo journalctl -u my-app.service \
--since "2026-02-19 09:00" \
--until "2026-02-19 12:00"
# 에러 로그만 필터링 (priority: emerg, alert, crit, err, warning, notice, info, debug)
sudo journalctl -u my-app.service -p err
# 부팅 이후 로그만 보기
sudo journalctl -u my-app.service -b
# JSON 형식으로 출력 (파싱용)
sudo journalctl -u my-app.service -o json-pretty -n 5
# journal 디스크 사용량 확인 및 정리
sudo journalctl --disk-usage
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=7d
-f 옵션은 장애 대응 시 실시간 로그를 확인할 때 유용합니다. -p err로 에러만 필터링하면 대량의 로그 속에서 문제를 빠르게 찾을 수 있습니다.
systemd 타이머
cron 대신 systemd 타이머를 사용하면 journal과 통합 로깅, 의존성 관리, 실행 실패 시 재시도 등의 이점이 있습니다. 타이머는 .timer 파일과 .service 파일 한 쌍으로 구성합니다.
매일 새벽 3시에 백업 스크립트를 실행하는 예제입니다.
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Database Backup
[Service]
Type=oneshot
User=deploy
ExecStart=/opt/scripts/backup.sh
# 실행 시간 제한 (30분)
TimeoutStartSec=1800
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3 AM
[Timer]
# 매일 새벽 3시 실행
OnCalendar=*-*-* 03:00:00
# 서버가 꺼져있다가 켜지면 놓친 실행을 즉시 수행
Persistent=true
# 실행 시간에 0~5분 랜덤 지연 (여러 타이머가 동시에 실행되는 것 방지)
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
# 타이머 활성화 + 시작
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# 등록된 타이머 목록 확인
sudo systemctl list-timers --all
# NEXT LEFT LAST PASSED UNIT ACTIVATES
# Tue 2026-02-20 03:00:00 KST 17h left - - backup.timer backup.service
# 타이머 수동 실행 (테스트)
sudo systemctl start backup.service
# 타이머 실행 로그 확인
sudo journalctl -u backup.service --since today
OnCalendar 표현식은 cron보다 가독성이 좋습니다. Mon *-*-* 09:00:00(매주 월요일 9시), *-*-01 00:00:00(매월 1일 자정) 같은 형식도 지원합니다.
서비스 디버깅
서비스가 시작되지 않을 때 원인을 찾는 순서입니다.
# 1. 상태 확인 — Active 상태와 에러 메시지 확인
sudo systemctl status my-app.service
# 2. 전체 로그 확인 — 시작 시도 시점부터의 로그
sudo journalctl -u my-app.service --no-pager
# 3. 서비스 파일 문법 검증
sudo systemd-analyze verify /etc/systemd/system/my-app.service
# 4. 의존성 그래프 확인
sudo systemctl list-dependencies my-app.service
# 5. 부팅 시간 병목 분석
sudo systemd-analyze blame
# 3.456s my-app.service
# 2.123s postgresql.service
# 1.234s nginx.service
자주 발생하는 문제와 해결법입니다.
| 증상 | 원인 | 해결 |
|---|---|---|
code=exited, status=203 | ExecStart 경로 오류 | 실행 파일 경로 확인, which 명령으로 검증 |
code=exited, status=217 | User가 존재하지 않음 | id 사용자명으로 확인 |
activating (auto-restart) 반복 | 서비스 즉시 종료 후 재시작 | 로그 확인, 환경변수/설정 파일 점검 |
inactive (dead) | enable 안 됨 | systemctl enable --now 실행 |
실전 팁
- 서비스 파일 위치: 직접 작성한 서비스는
/etc/systemd/system/에, 패키지 매니저가 설치한 서비스는/lib/systemd/system/에 위치합니다. 패키지 서비스를 수정하려면 원본을 편집하지 말고systemctl edit nginx.service로 오버라이드 파일을 생성하세요. - Restart=always vs on-failure: 웹 서버처럼 항상 떠 있어야 하는 서비스는
always, 배치 작업처럼 정상 종료가 의미 있는 경우on-failure를 사용합니다. - 타이머 vs cron: 새 프로젝트에서는 systemd 타이머를 권장합니다. journal 통합 로깅,
Persistent=true로 놓친 실행 보완, 의존성 관리 등 cron보다 관리 편의성이 높습니다. - 보안 강화:
ProtectSystem=strict,ProtectHome=true,NoNewPrivileges=true옵션으로 서비스의 파일시스템 접근을 제한할 수 있습니다. 프로덕션 서비스에는 적극 활용하세요. - 로그 관리: journal은 기본적으로 부팅 시마다 초기화됩니다.
/var/log/journal/디렉토리를 생성하면 영구 저장됩니다. 디스크 용량 관리를 위해SystemMaxUse=1G같은 설정을/etc/systemd/journald.conf에 추가하세요.