SSD Nodes Learn
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-07-24

VPS에서 systemd service로 프로그램 실행하는 방법

프로그램을 부팅 시 자동 실행하고 충돌 시 재시작하며 journal 로그를 남기는 systemd service 설정법을 설명합니다. 최소 단위의 unit file 작성법부터 timer를 이용한 스케줄링과 보안 강화를 위한 권한 설정까지 상세히 다룹니다.

systemd service의 정의와 필요성

systemd service는 프로그램 실행 방식을 정의하는 작은 텍스트 파일입니다. 이 파일은 부팅 시 프로그램 시작, 충돌 시 재시작, 출력 내용을 시스템 로그로 전송하는 역할을 수행합니다. SSH 세션에서 수동으로 실행한 프로그램은 로그아웃하거나 서버를 재부팅하면 종료됩니다. 반면 systemd service로 관리되는 프로그램은 쉘이 아닌 서버 시스템이 직접 관리하므로 계속 실행됩니다.

systemd는 Ubuntu, Debian, Fedora 및 대부분의 최신 Linux 서버에서 사용하는 init system입니다. systemd는 가장 먼저 실행되는 프로세스이며 다른 모든 프로세스를 감독합니다. 서비스 파일을 작성하면 해당 프로그램을 systemd 감독 체계에 등록하게 됩니다. 이 가이드에서는 작동 가능한 최소 단위의 설정법, 모든 유닛에 포함되는 3가지 섹션, 서비스 활성화 및 로그 확인 방법, timer를 이용한 스케줄 실행 방법, 그리고 최소 권한으로 실행하기 위한 보안 설정 방법을 설명합니다.

작동하는 가장 작은 서비스

서비스 파일은 /etc/systemd/system/에 위치하며, 확장자는 .service입니다. 단 몇 줄의 설정만 필요합니다. /usr/local/bin/myapp에 있는 프로그램을 위한 파일을 생성하십시오:

sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My application

[Service]
ExecStart=/usr/local/bin/myapp

[Install]
WantedBy=multi-user.target

이것은 완전하고 작동 가능한 유닛입니다. 실행할 명령은 ExecStart입니다. WantedBy=multi-user.target은 서버가 일반적인 multi-user operation 상태에 도달했을 때 서비스를 시작하라는 의미이며, 이 설정 덕분에 부팅 시 서비스가 실행됩니다. 그 외의 설정은 세부 조정 사항입니다.

세 가지 섹션과 각 섹션의 용도

모든 unit file은 대괄호로 구분된 섹션으로 나뉩니다. service는 세 가지 섹션을 사용합니다.

[Unit]는 service와 그 관계를 설명합니다. 가장 자주 사용하는 두 줄은 다음과 같습니다:

[Unit]
Description=My application
After=network-online.target
Wants=network-online.target

Descriptionsystemctl status에서 확인 가능한 사용자용 레이블입니다. After=network-online.target는 네트워크가 활성화될 때까지 program을 시작하지 않도록 systemd에 지시합니다. 이는 port를 바인딩하거나 외부로 연결을 시도하는 모든 작업에 중요합니다.

[Service]는 program의 실행 방식을 정의합니다. 대부분의 설정이 여기에 포함됩니다:

[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=info

User=myapp는 program을 root 대신 권한이 없는 계정으로 실행합니다. 이는 보안을 위해 가장 중요한 설정입니다. Restart=on-failureRestartSec=5는 별도의 섹션으로 구성됩니다. 대부분의 사용자가 service를 작성하는 이유가 바로 이 섹션들 때문입니다.

[Install]는 service를 enable할 때 발생하는 작업입니다:

[Install]
WantedBy=multi-user.target

WantedBy=multi-user.targetsystemctl enable를 실행할 때 service를 부팅 시 실행되도록 연결합니다. [Install] 섹션이 없으면 service를 수동으로 시작할 수는 있지만, 재부팅 후 자동으로 시작되지는 않습니다.

활성화 및 모니터링

unit file을 작성하거나 수정한 후에는 변경 사항을 반영하기 위해 systemd를 reload해야 합니다. 그 다음, 다음 명령어로 서비스를 활성화하고 동시에 시작하십시오:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

daemon-reload 단계는 자주 누락되는 과정입니다. systemd는 unit file을 캐시하므로, reload를 수행하기 전까지는 수정 사항이 적용되지 않습니다. enable --now 명령어를 사용하면 부팅 시 서비스가 자동으로 시작되도록 설정함과 동시에 서비스를 즉시 시작할 수 있습니다. 다음 명령어로 확인하십시오:

sudo systemctl status myapp.service
* myapp.service - My application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
     Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
   Main PID: 4123 (myapp)

원하는 결과는 Active: active (running)enabled입니다. 프로그램의 출력 내용을 확인하려면 journal에서 해당 unit의 로그만 조회하십시오:

sudo journalctl -u myapp.service -f

-ftail -f와 같이 새로운 로그가 생성될 때마다 실시간으로 출력합니다. 별도의 로깅 설정을 하지 않아도 프로그램이 standard output 또는 standard error로 출력하는 모든 내용이 여기에 기록됩니다.

실패 시 재시작, 이 기능의 목적

service의 주요 이점은 program이 종료되었을 때 systemd가 이를 재시작한다는 점입니다. 다음 두 줄로 이 기능을 설정합니다:

[Service]
Restart=on-failure
RestartSec=5

Restart=on-failure은 program이 non-zero 코드로 종료되거나 SIGKILL 또는 SIGSEGV와 같은 crash signal로 인해 종료될 때 program을 재시작합니다. 정상적인 종료 또는 SIGTERM, SIGINT, SIGHUP, SIGPIPE에 의한 중지는 재시작을 트리거하지 않습니다. RestartSec=5은 재시도 사이에 5초간 대기합니다. 이는 program이 즉시 crash될 때 무한 루프에 빠지는 것을 방지합니다. process를 kill하여 systemd가 이를 다시 실행하는지 확인하십시오. SIGKILL을 사용하십시오. 기본값인 SIGTERM은 정상 종료로 간주되므로 on-failure은 service를 재시작하지 않습니다:

sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.service

5초 이내에 status에 새로운 Main PIDactive (running)이 다시 나타납니다. 이것이 이 기능의 전부이며, service를 사용하는 것이 tmux 또는 screen에서 program을 실행하는 것보다 나은 이유입니다.

권한이 없는 사용자로 실행하고 보안을 강화하십시오

프로그램이 공격받을 경우 root 권한으로 실행 중인 서비스는 서버의 모든 권한을 탈취할 수 있습니다. 서비스 전용 사용자를 생성하여 실행하고, systemd 지시어를 사용하여 서비스의 권한을 제한하십시오. 먼저 로그인과 홈 디렉토리가 없는 시스템 계정을 생성합니다:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

그 다음 User=myapp을 설정하고 [Service]에 보안 강화 설정을 추가합니다:

[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

각 설정은 프로그램에 불필요한 권한을 제거합니다. NoNewPrivileges=true은 setuid 바이너리를 통해서라도 프로세스가 새로운 권한을 획득하는 것을 방지합니다. PrivateTmp=true은 다른 프로세스가 볼 수 없는 독립된 /tmp을 제공합니다. ProtectSystem=strictReadWritePaths=으로 지정한 경로를 제외한 전체 파일 시스템을 읽기 전용으로 만듭니다. ProtectHome=true/home을 완전히 숨깁니다. 이는 서비스를 방화벽 뒤에 배치하는 것과 같은 최소 권한 원칙입니다. 즉, 필요한 권한만 부여하는 것입니다. VPS에서 IPv6 방화벽 허점을 차단하는 방법 가이드를 읽었다면, 이 설정은 동일한 개념을 호스트 내부로 적용한 것입니다. 인터넷에 노출된 서비스라면 이 보안 강화 설정과 함께 SSH 앞단의 Fail2ban 및 기본 차단(default-deny) 방화벽을 함께 사용하십시오.

지시어를 잘못 입력하는 실수를 방지하기 위해, 모든 내용을 직접 입력하는 대신 완성된 보안 강화 유닛을 생성하여 복사하십시오:

Toolsystemd service and timer generator

Timers: the modern cron

systemd timer는 정해진 일정에 따라 service를 실행하며, cron job을 대체하는 현대적인 방식입니다. timer는 두 개의 파일로 구성됩니다. 작업을 수행하는 .timer와 실행 시간을 지정하는 .service입니다. 매일 새벽 3시에 backup을 수행한다고 가정하겠습니다. service는 작업을 한 번 수행한 후 종료됩니다.

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Type=oneshot는 프로그램이 상주하지 않고 실행 후 종료됨을 systemd에 알립니다. timer는 해당 작업을 스케줄링합니다.

# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar=*-*-* 03:00:00는 매일 새벽 3시를 의미합니다. systemd-analyze calendar "*-*-* 03:00:00"을 사용하여 모든 calendar expression을 테스트할 수 있습니다. 이 명령은 표현식이 올바르게 해석되는지 확인하고 다음 실행 시간을 출력합니다. 만약 새벽 3시에 서버가 꺼져 있었다면, Persistent=true은 서버가 다시 켜지는 즉시 누락된 작업을 실행합니다. 이는 cron이 수행할 수 없는 기능입니다. timer는 multi-user.target이 아니라 timers.target를 통해 enable됩니다. service가 아닌 timer를 enable하십시오.

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers

list-timers는 모든 timer와 다음 실행 및 마지막 실행 시간을 보여주므로, 작업의 다음 실행 시점을 즉시 확인할 수 있습니다. 위의 generator는 timer mode를 활성화할 때 쌍을 이루는 .service.timer를 자동으로 생성합니다. cron과 비교했을 때, timer는 journal을 통해 실제 로그를 제공하며, 모든 service와 동일한 hardening directive를 사용할 수 있고, Persistent=true이 제공하는 누락된 작업 재실행 기능을 지원합니다. 단순한 작업에는 cron도 유용하지만, 중요한 작업에는 timer가 더 적합한 도구입니다.

FAQ

systemd service와 cron job의 차이점은 무엇입니까?

service는 장시간 실행되는 프로그램을 유지합니다. 부팅 시 시작되고, 실패 시 재시작하며, journal에 로그를 기록합니다. cron job은 정해진 일정에 따라 짧은 명령어를 실행한 후 종료됩니다. 일정에 따른 실행이 필요하면서 journal 로그, hardening, 그리고 실행 실패 시 재시도 기능이 필요하다면 systemd timer를 사용하십시오. systemd timer는 .timer 일정과 oneshot service를 결합하여 대부분의 서버 작업에서 cron을 대체합니다.

systemd service 파일은 어디에 위치해야 합니까?

사용자 정의 unit 파일은 .service 확장자를 사용하여 /etc/systemd/system/ 디렉토리에 저장하십시오. 해당 디렉토리는 관리자가 추가하는 unit을 위한 것이며, /lib/systemd/system/에 포함된 패키지 제공 unit보다 우선순위가 높습니다. 파일을 생성하거나 수정한 후에는 sudo systemctl daemon-reload를 실행하여 systemd가 변경 사항을 인식하도록 하십시오.

서비스가 충돌했을 때 재시작하게 하려면 어떻게 합니까?

[Service] 섹션에 Restart=on-failureRestartSec=5를 추가한 다음, sudo systemctl daemon-reload를 실행하고 서비스를 재시작하십시오. systemd는 프로그램이 0이 아닌 종료 코드로 종료되거나 충돌 신호로 인해 종료될 때 프로그램을 다시 실행하며, 재시도 사이에는 5초의 대기 시간을 가집니다. sudo systemctl kill -s SIGKILL myapp.service로 테스트하십시오. 기본 신호인 SIGTERM은 정상 종료로 간주되어 on-failure를 트리거하지 않습니다. 몇 초 이내에 systemctl status에서 새로운 PID가 나타나는지 확인하십시오.

non-root 사용자로 systemd service를 실행하려면 어떻게 합니까?

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp를 사용하여 system account를 생성한 다음, [Service] 섹션에 User=myapp를 추가하십시오. 프로세스가 최소한의 권한만 갖도록 NoNewPrivileges=true, PrivateTmp=true, ProtectSystem=strict를 추가하십시오. 권한이 없는 사용자로 실행하는 것은 서비스 보안을 위해 수행할 수 있는 가장 중요한 변경 사항입니다.

서비스 시작에 실패한 이유는 무엇입니까?

요약 정보는 systemctl status myapp.service를, 전체 출력 내용은 journalctl -u myapp.service를 실행하여 확인하십시오. 가장 흔한 원인은 ExecStart의 잘못된 경로, WorkingDirectory 누락, User=의 파일 읽기 권한 오류, 또는 수정 후 sudo systemctl daemon-reload를 실행하지 않은 경우입니다. journal에는 프로그램 자체의 에러 메시지가 표시되며, 대개 문제 원인을 직접적으로 명시합니다.