Linux systemd 서비스 유닛 파일

애플리케이션을 systemd 서비스로 등록하기 위한 유닛 파일 템플릿입니다. 자동 재시작, 의존성 설정, 환경 변수, 리소스 제한, 로깅 등 프로덕션 환경에 필요한 설정을 포함합니다.

Gist
# /etc/systemd/system/my-app.service
# Node.js 웹 애플리케이션을 systemd 서비스로 등록하는 유닛 파일 예시
# 적용 후: sudo systemctl daemon-reload && sudo systemctl enable --now my-app

[Unit]
# 서비스 설명 (journalctl 및 systemctl status 에 표시됨)
Description=My Node.js Web Application

# 상세 문서 위치 (로컬 파일 또는 URL 가능)
Documentation=https://github.com/example/my-app

# 이 서비스가 시작되기 전에 반드시 실행되어야 할 유닛 목록
# network-online.target: 네트워크 완전 초기화 후 시작
After=network-online.target postgresql.service redis.service

# 위 유닛들이 실패하면 이 서비스도 시작하지 않음 (강한 의존성)
Requires=network-online.target

# 선택적 의존성: 있으면 좋지만 없어도 시작 가능
Wants=postgresql.service redis.service


[Service]
# 서비스 실행 유형
# simple  : ExecStart 프로세스가 메인 프로세스 (기본값)
# forking : 데몬처럼 fork 후 부모가 종료되는 경우
# notify  : sd_notify()로 준비 완료를 알리는 경우
# exec    : execve() 호출 후 메인 프로세스로 간주 (simple보다 안전)
Type=exec

# 서비스를 실행할 사용자 및 그룹 (root로 실행하지 말 것)
User=nodeapp
Group=nodeapp

# 작업 디렉토리 (상대 경로의 기준점)
WorkingDirectory=/opt/my-app

# 환경 변수 파일 로드 (파일이 없어도 오류 무시: 앞에 - 붙임)
EnvironmentFile=-/opt/my-app/.env

# 인라인 환경 변수 설정
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=LOG_LEVEL=info

# 실제 실행 명령 (절대 경로 사용 권장)
ExecStart=/usr/bin/node /opt/my-app/dist/server.js

# 서비스 중지 전 정리 명령 (graceful shutdown 신호)
ExecStop=/bin/kill -SIGTERM $MAINPID

# ExecStop 이후에도 프로세스가 살아있을 때 강제 종료 대기 시간 (초)
TimeoutStopSec=30

# 재시작 정책
# always    : 항상 재시작
# on-failure: 비정상 종료(0 이외 종료코드, 시그널) 시에만 재시작
# on-abnormal: 시그널, 타임아웃, watchdog 실패 시 재시작
Restart=on-failure

# 재시작 전 대기 시간 (초) - 폭발적 재시작 방지
RestartSec=5s

# 10초 내에 5번 이상 재시작 실패 시 서비스를 중지 상태로 전환
StartLimitIntervalSec=10
StartLimitBurst=5

# 표준 출력/오류를 journal로 전달 (journalctl -u my-app 으로 확인)
StandardOutput=journal
StandardError=journal

# journal 식별을 위한 syslog 태그
SyslogIdentifier=my-app

# ──────────────────────────────────────────────────
# 리소스 제한 (컨테이너 없이 프로세스 격리 효과)
# ──────────────────────────────────────────────────

# 최대 메모리 사용량 (초과 시 OOM killer 발동)
MemoryMax=512M

# 메모리 소프트 제한 (이 값 이상이면 재활용 대상)
MemoryHigh=400M

# CPU 사용률 제한 (단일 코어 기준 50%)
CPUQuota=50%

# CPU 가중치 (기본값 100, 높을수록 우선순위 높음)
CPUWeight=100

# 열 수 있는 최대 파일 디스크립터 수
LimitNOFILE=65536

# 최대 프로세스/스레드 수
LimitNPROC=512

# ──────────────────────────────────────────────────
# 보안 강화 설정
# ──────────────────────────────────────────────────

# /tmp를 프로세스 전용 임시 디렉토리로 격리
PrivateTmp=true

# /usr, /boot, /efi를 읽기 전용으로 마운트
ProtectSystem=strict

# /home, /root, /run/user 접근 차단
ProtectHome=true

# 새 파일 생성 시 권한 마스크 (0027 = 750)
UMask=0027

# 불필요한 커널 capabilities 제거
NoNewPrivileges=true

# 쓰기 가능한 경로 명시적 허용 (ProtectSystem=strict 와 함께 사용)
ReadWritePaths=/opt/my-app/logs /opt/my-app/tmp /var/lib/my-app


[Install]
# systemctl enable 시 어떤 target의 wants 디렉토리에 심볼릭 링크를 생성할지 지정
# multi-user.target: 텍스트 모드 다중 사용자 환경 (대부분의 서버)
WantedBy=multi-user.target