SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

Linux VPS에서 systemd 서비스 등록 및 자동 실행 방법

VPS에서 프로그램을 systemd 서비스로 등록하여 부팅 시 자동 실행하고 충돌 시 재시작하는 방법을 설명합니다. 유닛 파일 작성법부터 로그 확인, 보안을 위한 권한 제한까지 안정적인 서버 운영에 필요한 핵심 설정을 단계별로 안내합니다.

systemd 서비스란 무엇이며 왜 필요한가

systemd 서비스는 서버가 프로그램을 실행하는 방법을 정의하는 작은 텍스트 파일입니다. 부팅 시 시작, 충돌 시 재시작, 시스템 로그로 출력 전송과 같은 작업을 수행합니다. 이것이 서비스의 전부입니다. SSH 세션에서 수동으로 시작한 프로그램은 로그아웃하거나 서버가 재부팅되는 즉시 종료됩니다. 반면 systemd 서비스로 감싼 프로그램은 셸이 아닌 서버 자체가 소유하므로 계속 실행됩니다.

systemd는 Ubuntu, Debian, Fedora 및 대부분의 현대적인 Linux 서버에서 사용하는 init 시스템입니다. 시스템에서 가장 먼저 시작되어 다른 모든 프로세스를 관리하는 프로세스입니다. 항상 그랬던 것은 아니며, systemd가 이전의 init 스크립트를 어떻게 대체했는지에 관한 내용은 유닛 파일의 역할을 이해한 뒤 읽어볼 가치가 있습니다. 서비스 파일을 작성한다는 것은 프로그램을 이 관리자에게 맡긴다는 의미입니다. 이 가이드에서는 작동 가능한 가장 작은 단위의 유닛, 모든 유닛이 갖추어야 할 세 가지 섹션, 서비스 활성화 및 로그 확인 방법, 타이머를 이용한 주기적 실행 방법, 그리고 최소한의 권한으로 실행되도록 제한하는 방법을 설명합니다.

가장 단순한 서비스 구성

서비스 파일은 /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은 서버가 정상적인 다중 사용자 모드에 도달했을 때 서비스를 시작하라는 의미이며, 이를 통해 부팅 시 서비스가 자동으로 실행됩니다. 그 외의 모든 설정은 세부 조정에 해당합니다.

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

모든 유닛 파일은 대괄호로 묶인 섹션으로 구분됩니다. 서비스는 세 가지 섹션을 사용합니다.

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

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

Descriptionsystemctl status에서 볼 수 있는 사람이 읽을 수 있는 레이블입니다. After=network-online.target는 네트워크가 연결될 때까지 프로그램을 시작하지 말라고 systemd에 지시하며, 이는 포트를 바인딩하거나 외부 연결을 생성하는 모든 작업에 중요합니다.

[Service]은 프로그램이 실행되는 방식을 정의합니다. 대부분의 설정이 이곳에 들어갑니다.

[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은 root 대신 권한이 없는 계정으로 프로그램을 실행하며, 이는 보안을 위해 가장 중요한 한 줄입니다. 여기에는 Type= 줄이 없으므로 systemd는 simple로 대체하고 ExecStart 프로세스가 포그라운드에서 유지된다고 가정합니다. 백그라운드로 포크(fork)되는 프로그램은 시작 방식에 맞는 Type= 설정이 필요하며, 그렇지 않으면 실제 데몬이 종료되었음에도 유닛은 활성 상태로 보고됩니다. Restart=on-failureRestartSec=5는 서비스 파일을 작성하는 주된 이유이므로 아래에서 별도의 섹션으로 다룹니다.

[Install]은 서비스를 활성화할 때 발생하는 일을 정의합니다.

[Install]
WantedBy=multi-user.target

WantedBy=multi-user.targetsystemctl enable를 실행할 때 서비스를 부팅 과정에 연결하는 항목입니다. [Install] 섹션이 없으면 서비스를 수동으로 시작할 수는 있지만 재부팅 후 자동으로 실행되지 않습니다.

서비스 활성화 및 모니터링

unit 파일을 작성하거나 수정한 후에는 systemd를 리로드하여 변경 사항을 반영해야 합니다. 그 후 다음 명령으로 서비스를 활성화하고 시작합니다.

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

daemon-reload은 사용자가 자주 잊는 단계입니다. systemd는 unit 파일을 캐시하므로 리로드하지 않으면 수정 사항이 적용되지 않습니다. 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

-f 옵션은 tail -f와 마찬가지로 새로운 로그가 기록될 때마다 실시간으로 출력합니다. 프로그램이 표준 출력(stdout)이나 표준 에러(stderr)로 내보내는 모든 내용은 별도의 로깅 설정 없이도 이곳에 기록됩니다.

실패 시 재시작, 이 문서의 목적

서비스의 가장 큰 장점은 프로그램이 종료될 때 systemd가 이를 자동으로 다시 시작해 준다는 점입니다. 다음 두 줄이면 충분합니다.

[Service]
Restart=on-failure
RestartSec=5

Restart=on-failure은 프로그램이 0이 아닌 코드로 종료되거나 SIGKILL, SIGSEGV와 같은 크래시 신호로 인해 죽었을 때 프로그램을 다시 시작합니다. 정상적인 종료나 SIGTERM, SIGINT, SIGHUP, SIGPIPE 신호에 의한 정지는 재시작을 유발하지 않습니다. RestartSec=5는 재시작 시도 사이에 5초를 대기하므로, 즉시 크래시되는 프로그램이 짧은 루프를 반복하며 자원을 낭비하는 일을 방지합니다. 프로세스를 강제로 종료하고 systemd가 다시 살려내는지 확인해 보십시오. SIGKILL을 사용해야 합니다. 기본값인 SIGTERM은 정상적인 정지로 간주되므로 on-failure는 서비스를 재시작하지 않습니다.

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

5초 이내에 상태를 확인하면 새로운 Main PID과 함께 active (running)이 다시 실행 중임을 알 수 있습니다. 이것이 이 기능의 전부이며, 프로그램을 tmux이나 screen에서 단순히 실행해 두는 것보다 서비스로 관리하는 것이 더 나은 이유입니다.

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

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) 방화벽을 함께 사용하십시오.

이 모든 내용을 직접 입력하다가 지시어를 잘못 기억하는 실수를 범하는 대신, 보안이 강화된 전체 unit 파일을 생성하여 복사하십시오.

Toolsystemd service and timer generator

Timers: 현대적인 cron

systemd timer는 일정을 기반으로 서비스를 실행하며, cron job을 대체하는 현대적인 방식입니다. timer는 작업을 수행하는 .service와 실행 시점을 정의하는 .timer, 이렇게 두 개의 파일로 구성됩니다. 매일 오전 3시에 백업을 수행한다고 가정해 보겠습니다. 서비스는 작업을 한 번 수행하고 종료됩니다.

# /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"을 사용하면 달력 표현식을 테스트할 수 있으며, 구문이 올바른지 확인하고 다음에 실행될 시간을 출력합니다. Persistent=true는 서버가 오전 3시에 꺼져 있었을 경우 서버가 다시 켜지는 즉시 놓친 작업을 실행하며, 이는 cron이 할 수 없는 기능입니다. timer는 multi-user.target이 아닌 timers.target를 통해 활성화된다는 점에 유의하십시오. 서비스가 아닌 timer를 활성화해야 합니다.

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

list-timers은 모든 timer의 다음 실행 시간과 마지막 실행 시간을 보여주므로, 작업이 언제 실행되는지 한눈에 파악할 수 있습니다. 위 생성기는 timer 모드를 켤 때 .service.timer 쌍을 자동으로 생성합니다. cron 라인과 비교했을 때, timer는 journal을 통해 실제 로그를 제공하고, 일반 서비스와 동일한 보안 지시문을 적용할 수 있으며, Persistent=true이 제공하는 놓친 작업 실행 기능을 사용할 수 있습니다. 단순한 작업에는 여전히 cron이 적합하지만, 작업의 중요도가 높다면 timer가 더 나은 도구입니다.

FAQ

systemd 서비스와 cron 작업의 차이점은 무엇입니까?

서비스는 장시간 실행되는 프로그램을 유지합니다. 부팅 시 시작되고, 실패 시 재시작하며, 저널에 로그를 기록합니다. cron 작업은 짧은 명령을 일정에 따라 실행한 뒤 종료됩니다. 스케줄링 기능과 함께 저널 로그, 보안 강화, 놓친 작업의 보충 실행이 필요하다면 systemd timer를 사용하십시오. 이는 .timer 스케줄과 oneshot 서비스를 결합한 형태로, 대부분의 서버 작업에서 cron을 대체합니다.

systemd 서비스 파일은 어디에 두어야 합니까?

직접 작성한 유닛 파일은 /etc/systemd/system/에 두어야 하며, 파일명은 .service로 끝나야 합니다. 해당 디렉터리는 관리자가 추가하는 유닛을 위한 공간이며, /lib/systemd/system/에 패키지로 제공되는 유닛보다 우선순위가 높습니다. 파일을 생성하거나 수정한 뒤에는 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가 나타나는 것을 확인할 수 있습니다.

systemd 서비스를 root가 아닌 사용자로 실행하려면 어떻게 합니까?

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp를 사용하여 시스템 계정을 생성한 뒤, [Service] 섹션에 User=myapp를 추가하십시오. 프로세스가 필요한 최소한의 권한만 갖도록 NoNewPrivileges=true, PrivateTmp=true, ProtectSystem=strict를 추가하십시오. 권한이 없는 사용자로 실행하는 것은 서비스의 안전성을 높이는 가장 중요한 단일 조치입니다.

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

요약 정보를 보려면 systemctl status myapp.service을, 전체 출력을 보려면 journalctl -u myapp.service을 실행하십시오. 가장 흔한 원인은 ExecStart의 잘못된 경로, 누락된 WorkingDirectory, User=가 파일을 읽을 수 없어 발생하는 권한 오류, 또는 편집 후 잊어버린 sudo systemctl daemon-reload입니다. 저널에는 프로그램 자체의 오류 메시지가 표시되며, 일반적으로 문제의 원인을 직접적으로 명시합니다.