如何在 VPS 上使用 systemd 將程式設為服務
本文教學如何撰寫 systemd service 檔案,讓程式在 VPS 開機時自動啟動、崩潰時自動重啟並將日誌傳送至 journal。內容涵蓋 unit file 的三個核心區段、如何設定 timer 排程,以及如何透過非 root 帳號執行以強化安全性,是 Linux 伺服器管理必學技巧。
什麼是 systemd service,以及為何需要它
systemd service 是一個小型文字檔,用來定義伺服器執行程式的方式:包含開機時啟動、程式崩潰時重啟,並將輸出傳送到系統日誌。這就是其核心功能。在 SSH 階段手動啟動的程式,會在登出或伺服器重啟時立即停止。而透過 systemd service 封裝的程式會持續執行,因為該程式是由伺服器系統而非您的 shell 所管理。
systemd 是 Ubuntu、Debian、Fedora 以及大多數現代 Linux 伺服器的 init system。它是系統啟動後的第一個進程,負責監督所有其他進程。當您撰寫 service 檔案時,即是將程式交由該監督者管理。本指南將介紹最精簡的運作單元、每個單元必備的三個區段、如何啟用並讀取日誌、如何搭配 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.targetDescription 是您在 systemctl status 中看到的標籤。After=network-online.target 告知 systemd 在網路啟動前不要啟動您的程式,這對於需要綁定 port 或進行外連的程式至關重要。
[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=infoUser=myapp 以非特權帳號而非 root 執行程式,這是確保安全性最重要的設定。Restart=on-failure 與 RestartSec=5 擁有獨立的區段,因為這兩項是大多數人建立 service 的主要原因。
[Install] 定義啟用 service 時執行的動作:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target 在執行 systemctl enable 時將 service 連結至開機流程。若缺少 [Install] 區段,service 雖可手動啟動,但在重新開機後不會自動啟動。
啟用並觀察執行狀況
修改或編輯任何 unit file 後,必須 reload systemd 以載入變更,接著即可透過單一指令完成 enable 與 start 服務:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload 是最常被忽略的步驟:systemd 會快取 unit files,因此在 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。若要查看該程式的輸出,請針對該 unit 查詢 journal:
sudo journalctl -u myapp.service -f-f 會像 tail -f 一樣即時追蹤新產生的行。程式寫入 standard output 或 standard error 的所有內容都會記錄於此,您無需額外設定 logging。
失敗時重啟:您在此閱讀的原因
使用 service 的主要優點是當程式停止執行時,systemd 會自動重啟該程式。只需設定兩行指令:
[Service]
Restart=on-failure
RestartSec=5當程式以非零狀態碼退出,或因 SIGKILL 或 SIGSEGV 等崩潰訊號停止時,Restart=on-failure 會重啟程式。若為正常退出,或因 SIGTERM、SIGINT、SIGHUP 或 SIGPIPE 停止,則不會觸發重啟。RestartSec=5 在每次嘗試之間會等待 5 秒,避免程式在發生即時崩潰時進入無止盡的迴圈。您可以透過殺死該 process 並觀察 systemd 重新啟動它來進行驗證。請使用 SIGKILL:由於預設的 SIGTERM 被視為正常停止,因此 on-failure 不會重啟該 service:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.service在 5 秒內,status 會再次顯示新的 Main PID 與 active (running)。這就是此功能的核心,也是為何使用 service 優於直接在 tmux 或 screen 中執行程式的原因。
以非特權使用者執行並進行強化
若程式被漏洞利用,以 root 身份執行的服務將能對伺服器進行任何操作。請使用專屬使用者執行該服務,並在 systemd 中加入指令以限制其權限。首先,建立一個無登入權限且無 home 目錄的系統帳號:
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 binary 也無法達成。PrivateTmp=true 提供一個其他程序無法檢視的私有 /tmp。ProtectSystem=strict 會將整個檔案系統設為唯讀,除非您透過 ReadWritePaths= 指定特定路徑。ProtectHome=true 會完全隱藏 /home。這與在防火牆後方部署服務的最小權限原則相同:僅提供必要的權限。如果您已閱讀過 在 VPS 上修補 IPv6 防火牆漏洞 的指南,這就是該概念在主機端的應用。對於面對網際網路的服務,請將此強化措施與 在 SSH 前部署 Fail2ban 以及預設拒絕(default-deny)的防火牆搭配使用。
與其手動輸入所有指令並可能記錯指令,不如直接產生一個完整的強化 unit 並將其複製出來:
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.shType=oneshot 告知 systemd 該程式執行完畢後會結束,而非持續常駐。Timer 負責排定其執行時間:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 代表每天凌晨 3 點。您可以使用 systemd-analyze calendar "*-*-* 03:00:00" 測試任何日曆表達式,它會確認解析結果並列印出下次執行的時間。若伺服器在凌晨 3 點時處於關機狀態,Persistent=true 會在伺服器重新啟動後立即執行錯過的任務,這是 cron 無法做到的。請注意,timer 是透過 timers.target 啟用,而非 multi-user.target。請啟用 timer 而非 service:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers 會顯示所有 timer 及其下次與上次執行時間,讓您能一眼看出任務下次執行的時間。上述的 generator 會在您開啟 timer 模式時,自動為您建立配對的 .service 與 .timer。相較於 cron,timer 提供 journal 中的真實日誌、與一般 service 相同的強化指令,以及 Persistent=true 所提供的錯過任務補執行功能。對於簡單任務,使用 cron 即可;但對於重要的任務,timer 是更好的工具。
FAQ
systemd service 與 cron job 有何不同?
Service 會維持程式持續執行:它會在開機時啟動、失敗時重啟,並將日誌記錄至 journal。Cron job 則是在排定的時間執行短暫指令後結束。若您需要排程功能,同時也需要 journal 日誌、安全性強化(hardening)以及補償錯過的執行次數,請使用 systemd timer。它將 .timer 排程與 oneshot service 結合,並取代 cron 處理大多數伺服器任務。
systemd service 檔案應放在哪裡?
請將自定義的 unit 檔案放在 /etc/systemd/system/,並以 .service 結尾。該目錄用於管理員新增的 unit,其優先權高於 /lib/systemd/system/ 中的套件 unit。建立或編輯檔案後,請執行 sudo systemctl daemon-reload 以讓 systemd 載入變更。
如何設定服務在崩潰時自動重啟?
在 [Service] 區段中加入 Restart=on-failure 與 RestartSec=5,接著執行 sudo systemctl daemon-reload 並重啟服務。當程式以非零代碼退出或因崩潰訊號停止時,systemd 會重新啟動該程式,且兩次嘗試之間會等待 5 秒。可以使用 sudo systemctl kill -s SIGKILL myapp.service 進行測試:預設訊號 SIGTERM 視為正常停止,不會觸發 on-failure;接著觀察 systemctl status 是否在幾秒內顯示新的 PID。
如何以非 root 使用者身份執行 systemd service?
使用 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。journal 會顯示程式本身的錯誤訊息,通常會直接說明問題所在。