Linux 서버 관리의 핵심인 systemd는 단순한 init 시스템을 넘어 서비스 관리, 로깅, 네트워크, 타이머, 리소스 제어까지 담당하는 종합 시스템 매니저다. 2026년 현재 거의 모든 주요 배포판이 systemd를 채택하고 있으며, 최신 버전에서는 컨테이너 통합과 보안 기능이 크게 강화되었다. 실무에서 바로 사용할 수 있는 systemd 핵심 기능을 상세히 다룬다.

서비스 유닛 파일 작성

커스텀 서비스를 systemd에 등록하는 것이 첫 번째 단계다. 유닛 파일은 /etc/systemd/system/ 디렉토리에 작성한다.

기본 서비스 파일

# /etc/systemd/system/my-api.service
[Unit]
Description=My API Server
Documentation=https://docs.example.com
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service

[Service]
Type=exec
User=appuser
Group=appgroup
WorkingDirectory=/opt/my-api

# 환경변수
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/opt/my-api/.env

# 실행 명령
ExecStartPre=/usr/bin/npm run migrate
ExecStart=/usr/bin/node dist/server.js
ExecReload=/bin/kill -HUP $MAINPID

# 재시작 정책
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3

# 로깅
StandardOutput=journal
StandardError=journal
SyslogIdentifier=my-api

[Install]
WantedBy=multi-user.target

서비스 관리 명령어

# 유닛 파일 변경 후 데몬 리로드
sudo systemctl daemon-reload

# 서비스 시작/중지/재시작
sudo systemctl start my-api
sudo systemctl stop my-api
sudo systemctl restart my-api

# 설정 리로드 (프로세스 재시작 없이)
sudo systemctl reload my-api

# 부팅 시 자동 시작 등록/해제
sudo systemctl enable my-api
sudo systemctl disable my-api

# 즉시 시작 + 자동 시작 등록
sudo systemctl enable --now my-api

# 상태 확인
systemctl status my-api

# 실패 원인 상세 확인
systemctl show my-api --property=Result,ExecMainStatus,ActiveEnterTimestamp
journalctl -u my-api --since "5 min ago" --no-pager

systemd 타이머 (cron 대체)

systemd 타이머는 cron보다 유연하고 로깅이 통합되며, 서비스 의존성을 설정할 수 있다.

정기 백업 타이머 설정

# /etc/systemd/system/db-backup.service
[Unit]
Description=Database Backup
After=postgresql.service

[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup-db.sh
EnvironmentFile=/opt/scripts/.backup-env

# 타임아웃 설정
TimeoutStartSec=600  # 10분

# 실패 시 알림
ExecStopPost=/opt/scripts/notify-on-failure.sh
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Daily Database Backup Timer

[Timer]
# 매일 새벽 2시 실행
OnCalendar=*-*-* 02:00:00

# 서버가 꺼져있었으면 부팅 후 실행
Persistent=true

# 랜덤 지연 (여러 서버의 동시 실행 분산)
RandomizedDelaySec=300  # 0~5분 랜덤

# 정확도 (기본 1분)
AccuracySec=1s

[Install]
WantedBy=timers.target
# 타이머 활성화
sudo systemctl enable --now db-backup.timer

# 타이머 목록 확인
systemctl list-timers --all

# 다음 실행 시각 확인
systemctl show db-backup.timer --property=NextElapseUSecRealtime

# 수동 즉시 실행 (테스트)
sudo systemctl start db-backup.service

# 실행 이력 확인
journalctl -u db-backup.service --since today

다양한 OnCalendar 표현식

# 매시간 정각
OnCalendar=*-*-* *:00:00

# 매 15분마다
OnCalendar=*:0/15

# 평일 오전 9시
OnCalendar=Mon..Fri *-*-* 09:00:00

# 매월 1일, 15일
OnCalendar=*-*-01,15 00:00:00

# 매주 월요일
OnCalendar=weekly
# 또는: Mon *-*-* 00:00:00

# 1시간 간격으로 부팅 후 시작
OnBootSec=5min
OnUnitActiveSec=1h

# 표현식 검증
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
# Next elapse: Mon 2026-04-06 09:00:00 KST

리소스 제한 (cgroups v2)

systemd는 cgroups v2를 통해 서비스별 CPU, 메모리, I/O를 정밀하게 제한할 수 있다.

# /etc/systemd/system/my-api.service.d/limits.conf
# (드롭인 파일로 기존 서비스에 리소스 제한 추가)
[Service]
# CPU 제한
CPUQuota=200%        # 최대 CPU 2코어
CPUWeight=100        # 상대적 CPU 가중치 (기본 100)

# 메모리 제한
MemoryMax=2G         # 절대 최대 메모리
MemoryHigh=1536M     # 소프트 제한 (초과 시 스로틀링)
MemorySwapMax=0      # 스왑 금지

# I/O 제한
IOWeight=50          # I/O 가중치 (기본 100)
IOReadBandwidthMax=/dev/sda 100M  # 읽기 대역폭
IOWriteBandwidthMax=/dev/sda 50M  # 쓰기 대역폭

# 프로세스 수 제한
TasksMax=256

# 파일 디스크립터 제한
LimitNOFILE=65536
# 드롭인 파일 적용
sudo systemctl daemon-reload
sudo systemctl restart my-api

# 현재 리소스 사용량 확인
systemctl show my-api --property=MemoryCurrent,CPUUsageNSec,TasksCurrent

# cgroup 실시간 모니터링
systemd-cgtop

# 특정 서비스의 cgroup 경로 확인
systemctl show my-api --property=ControlGroup

보안 강화 (샌드박싱)

systemd는 서비스별 보안 격리 기능을 내장하고 있다.

# /etc/systemd/system/my-api.service 보안 설정
[Service]
# 파일시스템 보호
ProtectSystem=strict       # /usr, /boot, /efi 읽기 전용
ProtectHome=true           # /home, /root, /run/user 접근 차단
ReadWritePaths=/opt/my-api/data /var/log/my-api
PrivateTmp=true            # 전용 /tmp 디렉토리

# 네트워크 제한
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
IPAddressAllow=10.0.0.0/8 172.16.0.0/12

# 커널 보호
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true

# 기타 보안
NoNewPrivileges=true       # 권한 상승 금지
PrivateDevices=true        # 디바이스 접근 차단
ProtectClock=true          # 시스템 시간 변경 금지
ProtectControlGroups=true  # cgroup 수정 금지
RestrictRealtime=true      # 실시간 스케줄링 금지
RestrictSUIDSGID=true      # SUID/SGID 파일 생성 금지
SystemCallArchitectures=native  # 네이티브 아키텍처만

# 시스템콜 필터링
SystemCallFilter=@system-service
SystemCallFilter=~@mount @reboot @swap @debug
# 서비스 보안 점수 확인
systemd-analyze security my-api

# 출력 예시:
# my-api.service: 2.3 OK
# (0.0이 최고, 10.0이 최악)

# 전체 서비스 보안 점수 일괄 확인
systemd-analyze security

journalctl 고급 활용

# 특정 서비스 로그
journalctl -u my-api -f          # 실시간 팔로우
journalctl -u my-api -n 100      # 최근 100줄
journalctl -u my-api --since today
journalctl -u my-api --since "2026-04-05 09:00" --until "2026-04-05 12:00"

# 우선순위별 필터링
journalctl -u my-api -p err      # 에러 이상만
journalctl -u my-api -p warning  # 경고 이상만

# JSON 출력 (파이프라인 연동)
journalctl -u my-api -o json --since today | jq '.MESSAGE'

# 부팅 관련 로그
journalctl -b           # 현재 부팅 로그
journalctl -b -1        # 이전 부팅 로그
journalctl --list-boots  # 부팅 이력

# 디스크 사용량 관리
journalctl --disk-usage
sudo journalctl --vacuum-size=500M  # 500MB로 제한
sudo journalctl --vacuum-time=30d   # 30일 이전 삭제

# /etc/systemd/journald.conf 영구 설정
[Journal]
Storage=persistent
SystemMaxUse=1G
MaxRetentionSec=90d
Compress=yes

유용한 systemd 진단 명령어

# 부팅 시간 분석
systemd-analyze
systemd-analyze blame     # 서비스별 시작 시간
systemd-analyze critical-chain  # 크리티컬 패스

# 의존성 트리 확인
systemctl list-dependencies my-api

# 실패한 서비스 확인
systemctl --failed

# 모든 서비스 상태 한눈에
systemctl list-units --type=service --state=running

# 서비스가 사용하는 포트 확인
systemctl show my-api --property=Listen
ss -tlnp | grep node

실무 팁

  1. 유닛 파일을 직접 수정하지 말고 드롭인 디렉토리(*.service.d/)를 활용하면 패키지 업데이트 시 충돌을 방지할 수 있다
  2. ExecStart에 절대 경로를 항상 사용해야 한다. PATH를 신뢰하지 말 것
  3. Type=exec은 Type=simple보다 프로세스 준비 상태를 정확하게 감지한다
  4. 보안 설정은 systemd-analyze security로 점수를 확인하며 점진적으로 적용
  5. 타이머의 Persistent=true는 서버 다운타임 동안 누락된 실행을 보장한다