리눅스 systemd 서비스 만들기 (unit 파일 작성, 자동 재시작, timer)
직접 만든 스크립트나 Java 애플리케이션을 서버에서 계속 돌리고 싶을 때, nohup이나 &로 띄워 두면 서버가 재부팅되거나 프로세스가 죽었을 때 아무도 다시 살려 주지 않는다. 이럴 때는 systemd에 서비스로 등록하는 것이 정석이다. 이 글은 unit 파일 작성부터 등록, 자동 재시작, 로그 확인, 그리고 crontab 대신 쓰는 timer까지 systemd 서비스 만들기 과정을 순서대로 정리한다. start, stop 같은 기본 명령은 리눅스 서비스 관리(systemctl) 글에 먼저 정리해 두었다.

systemd 서비스로 등록하면 좋은 점
- 서버가 재부팅되면 자동으로 다시 시작된다.
- 프로세스가 비정상 종료되면 정해진 규칙대로 자동 재시작한다.
- 표준 출력과 에러가 journald 로그에 모여 journalctl로 바로 볼 수 있다.
- 실행 사용자, 작업 디렉터리, 환경 변수를 한 파일에서 관리한다.
- systemctl start, stop, status 같은 같은 명령으로 모든 서비스를 다룰 수 있다.
unit 파일 위치와 구조
직접 만드는 서비스 파일은 /etc/systemd/system/ 아래에 이름.service로 만든다. 패키지가 설치한 서비스는 /lib/systemd/system/ 또는 /usr/lib/systemd/system/에 있는데, 이 파일들은 업데이트 때 덮어써지므로 직접 고치지 않는다.
| 섹션 | 역할 | 주요 항목 |
|---|---|---|
| [Unit] | 서비스 설명과 실행 순서 | Description, After, Wants |
| [Service] | 실제 실행 방법 | ExecStart, User, Restart, Environment |
| [Install] | 부팅 시 어디에 붙을지 | WantedBy=multi-user.target |
systemd 서비스 만들기 1단계 실행할 스크립트 준비
예제로 10초마다 시간을 기록하는 간단한 스크립트를 서비스로 만들어 보자. 스크립트 작성법은 리눅스 쉘 스크립트 기초에 있다.
sudo mkdir -p /opt/myapp
sudo tee /opt/myapp/run.sh > /dev/null <<'EOF'
#!/bin/bash
while true; do
echo "[$(date '+%F %T')] myapp is running (env=$APP_ENV)"
sleep 10
done
EOF
sudo chmod +x /opt/myapp/run.sh
# 서비스 전용 계정 (로그인 불가)
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp서비스는 root가 아닌 전용 계정으로 돌리는 것이 안전하다. 사용자 관리 명령은 리눅스 사용자 관리를 참고하자.
systemd 서비스 만들기 2단계 unit 파일 작성
sudo vi /etc/systemd/system/myapp.service
[Unit]
Description=My sample application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=APP_ENV=prod
ExecStart=/opt/myapp/run.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target| 항목 | 뜻 |
|---|---|
After=network-online.target | 네트워크가 준비된 뒤에 시작 |
Type=simple | ExecStart 프로세스 자체가 서비스(가장 흔한 형태) |
User / Group | 실행 계정 |
WorkingDirectory | 작업 디렉터리 |
Environment | 환경 변수(여러 개면 여러 줄) |
ExecStart | 실행 명령, 반드시 절대 경로 |
Restart=on-failure | 비정상 종료 시에만 재시작(항상 재시작은 always) |
RestartSec=5 | 재시작 전 5초 대기 |
환경 변수가 많다면 EnvironmentFile=/etc/myapp/app.env처럼 파일로 분리한다. systemd 서비스는 .bashrc를 읽지 않는다는 점은 리눅스 환경 변수와 PATH 설정에서도 다뤘다.
systemd 서비스 만들기 3단계 등록하고 실행하기
# unit 파일을 새로 만들거나 고친 뒤에는 항상
sudo systemctl daemon-reload
# 부팅 시 자동 시작 등록 + 지금 바로 시작
sudo systemctl enable --now myapp
# 상태 확인
systemctl status myapp
# 로그 실시간 보기
journalctl -u myapp -fstatus에 active (running)이 보이고, journalctl에 10초마다 로그가 찍히면 성공이다. 로그 보는 방법은 journalctl 사용법에 더 자세히 정리했다.
자동 재시작 확인해 보기
# 프로세스를 강제로 죽여 본다
sudo systemctl kill -s SIGKILL myapp
sleep 6
systemctl status myapp # 다시 running 이면 Restart 설정이 동작한 것Java 애플리케이션 예시
Spring Boot 같은 jar 파일도 같은 방식으로 등록한다. Java 설치는 자바 JDK 설치 방법을 참고하자.
[Unit]
Description=My Spring Boot App
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/app.env
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/myapp/app.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetJVM은 SIGTERM을 받고 정상 종료할 때 종료 코드 143을 돌려준다. SuccessExitStatus=143을 넣어야 systemctl stop 할 때 실패로 표시되지 않는다. 앞단에 웹 서버를 두려면 Nginx 리버스 프록시로 연결하면 된다.
설정 수정과 삭제
# 원본은 두고 덮어쓸 설정만 추가 (override.conf 생성)
sudo systemctl edit myapp
# 최종 적용된 설정 보기
systemctl cat myapp
# 서비스 삭제
sudo systemctl disable --now myapp
sudo rm /etc/systemd/system/myapp.service
sudo systemctl daemon-reloadcrontab 대신 systemd timer 쓰기
정해진 시간에 한 번 실행하는 작업도 systemd로 만들 수 있다. crontab보다 설정 파일이 두 개로 늘지만, 실행 기록이 journalctl에 남고 서버가 꺼져 있던 동안 놓친 실행을 켜진 뒤 따라잡을 수 있다(Persistent=true). crontab 사용법과 비교해 보자.
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
ExecStart=/home/admin/scripts/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup every day at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers # 다음 실행 시각 확인
journalctl -u backup.service # 실행 기록systemd 서비스 만들기 오류 해결
| 증상 | 원인과 해결 |
|---|---|
| status=203/EXEC | ExecStart 경로가 틀렸거나, 실행 권한(chmod +x)이 없거나, 스크립트 첫 줄 #!/bin/bash가 없다 |
| status=217/USER | User에 적은 계정이 없다 |
| Permission denied | 실행 계정이 파일이나 디렉터리에 접근할 권한이 없다 |
| 고쳤는데 그대로 | daemon-reload를 하지 않았다 |
| command not found | PATH가 짧다. 명령을 절대 경로로 쓴다 |
# 실패 원인 자세히 보기
journalctl -xeu myapp
systemd-analyze verify /etc/systemd/system/myapp.service옵션 전체는 systemd.service 공식 매뉴얼과 systemd.timer 공식 매뉴얼에서 볼 수 있다.
마무리
nohup으로 띄운 프로세스는 언젠가 조용히 사라진다. 계속 돌아야 하는 프로그램이라면 unit 파일 하나를 만들어 systemd에 맡기자. 전용 계정, 절대 경로, Restart=on-failure, 그리고 daemon-reload 네 가지만 기억하면 대부분의 서비스를 안정적으로 운영할 수 있다. 컨테이너로 운영하는 방법은 Docker 설치와 기본 명령어에, 새 서버의 전체 설정 순서는 리눅스 서버 초기 세팅 체크리스트에 있다.
첫 댓글을 남겨보세요