為什麼 systemd 沒有重新啟動服務?
systemd 的 Restart= 只監控主要程序。同一個 cgroup 內的子程序即使終止也可能被忽略,本文說明 Type=、重啟限制與 journal 的實際行為。
簡短答案:systemd restart policy 只監控一個程序
systemd restart policy 會監控每個 unit 中的一個程序,也就是主要程序。Restart= 只會讀取該程序的結束狀態,不會讀取其他資訊。某個 unit 的 control group 可以包含 20 個程序,其中一個程序可能終止,但只要主要程序仍在執行,該 unit 就會維持 active (running)。對 systemd 而言沒有任何失敗,因此也不會重新啟動。
systemd 確實知道其他程序的存在。unit 停止時,systemd 會終止這些程序;計算 unit 的資源限制時,會將這些程序的記憶體用量計入;也會對這些程序套用 unit 的 CPU 配額,並在 systemctl status 中列出它們。但 systemd 從不讀取這些程序的結束狀態。restart logic 與 cgroup 是兩個不同的機制,本指南大多在說明兩者之間的落差。
cgroup 會保存什麼,以及重新啟動邏輯會讀取什麼
cgroup(control group)是由核心管理、負責管理一組程序的物件。每個服務單元都會取得一個 cgroup,名稱與單元相同。程序無法離開該 cgroup。子程序會繼承父程序的 cgroup,而非特權程序無法自行移動到其他位置。因此,systemd 能清理由 daemon 建立多層子程序的程序樹;舊式 init script 無法可靠地做到這一點。
並列查看這兩項事實:
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.servicesystemd-cgls 會列出單元中的每個程序。MainPID 是重新啟動政策讀取的唯一數值。當兩者與你的認知模型不一致時,該不一致就是問題所在。MainPID=0 比 PID 錯誤更嚴重:這表示 systemd 根本沒有追蹤任何程序,因此任何 Restart= 值都不可能生效。
主要程序規則有一個真正的例外。如果 kernel 的 out-of-memory killer 終止單元 cgroup 中的任何程序,systemd 仍然會察覺,因為它會監看 cgroup 的 memory.events 檔案。OOMPolicy= 會決定接下來的處理方式,預設值為 stop:整個單元會停止,結果會記錄為 oom-kill,並視為失敗,因此 Restart=on-failure 會生效。journal 會清楚記錄這件事。
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Failed with result 'oom-kill'.因此,因記憶體不足而被終止的子程序確實會導致單元停止;同一個子程序若因 segmentation fault 終止,則不會造成相同結果。如果你對單元設定記憶體限制,請先閱讀 MemoryMax 和 CPUQuota 如何套用至單元的 cgroup,再調整重新啟動政策,因為這兩項功能只有在此處會互相作用。
Type= 如何選定主要程序
Type= 位於 [Service] 區段時,不只是用來決定啟動順序。它也定義哪個 PID(process ID)會成為 MainPID,也就是決定 Restart= 能看到什麼。
Type=simple是預設值。systemd 從ExecStart=建立的程序就是主要程序。systemd 會立即將 unit 標記為已啟動,甚至還不知道exec是否成功執行。二進位檔路徑拼寫錯誤時,啟動工作仍會成功,接著Main process exited, code=exited, status=203/EXEC會在片刻後發生。Type=exec的行為類似simple,但啟動工作會等到exec成功後才完成。這會讓上述的拼寫錯誤明確呈現為啟動失敗。它需要 systemd 240 或更新版本,所有仍受支援的發行版都符合此條件。請優先使用它,而不是simple。Type=forking預期ExecStart=啟動背景 daemon,然後結束。systemd 會等待父程序結束,再尋找真正的 daemon。請提供PIDFile=。如果未提供,預設啟用的GuessMainPID=只有在 cgroup 中恰好剩下單一程序時才能正常運作。若留下兩個程序,MainPID會維持為0。Type=notify表示服務會呼叫sd_notify(3),並在能夠處理網路流量時傳送READY=1。它也可以傳送MAINPID=,將另一個程序交給 systemd 追蹤。NotifyAccess=預設為main,因此子程序傳送的通知會被忽略,而 journal 會記錄傳送通知的 PID。Type=oneshot沒有持續存在的主要程序。除非設定RemainAfterExit=yes,否則ExecStart=完成後 unit 就會立即變成 inactive。這裡會拒絕Restart=always與Restart=on-success,並顯示訊息Service has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.。包括on-failure在內的其他值都會被接受。
有兩個 Type=forking 錯誤值得記住,因為它們都會讓 unit 在沒有明顯原因的情況下看似故障:
myapp.service: Can't open PID file /run/myapp.pid (yet?) after start: No such file or directory
myapp.service: New main PID 4711 does not belong to service, and PID file is not owned by root. Refusing.第一個表示 daemon 將 PID 檔案寫入其他位置,或寫入時間晚於 systemd 的檢查時間。第二個表示 PID 檔案指定了 unit cgroup 外部的程序。systemd 會拒絕接管該程序,因為如果 PID 檔案可由程序寫入,就可能被用來讓 systemd 向系統上的任意程序傳送訊號。
為什麼 wrapper script 會掩蓋子程序的結束
以下是會產生標題所述問題的結構。
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait此 unit 是 Type=simple,因此主程序是 shell。wait 不帶引數時,只有在所有子程序結束後才會返回。終止 worker 後,shell 仍會等待 web process,因此 shell 不會結束,MainPID 也不會結束,Restart= 因而永遠不會被檢查。此時 cgroup 只剩較少的程序,systemctl status 會顯示較短的程序樹,而 unit 仍是 active (running)。systemd 不會監看該程序樹的變化。
同一錯誤的另一種形式更不容易察覺:
ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'主程序是 shell,不是 myapp。在 systemctl stop 時,systemd 會將 SIGTERM 傳送給主程序,而正在等待前景子程序的 shell 不會轉送該訊號。停止程序接著會完整等待 TimeoutStopSec,預設為 90 秒,最後會如下結束:
myapp.service: State 'stop-sigterm' timed out. Killing.
myapp.service: Killing process 4711 (myapp) with signal SIGKILL.修正方式是 exec。加入 exec /usr/local/bin/myapp 後,shell 會由該程式取代,因此 MainPID 會是該程式,訊號也能傳送到它。更好的做法是刪除 shell,並在 unit 中使用 Environment= 或 EnvironmentFile=。請注意,當 -c 字串只包含單一命令時,這個錯誤會自行隱藏,因為 bash 和 dash 都會將該情況最佳化為直接執行 exec。若在字串中加入第二個命令,shell 就會留在程式前方持續執行。
在測試 VPS 上於 2 分鐘內重現
將上述 wrapper 儲存為 /usr/local/bin/two-children.sh,使用 chmod +x 將其設為可執行,並將兩個程式路徑替換為 sleep 3600。使用 Type=simple 和 Restart=on-failure 讓 unit 指向該 wrapper,接著執行 systemctl daemon-reload 並啟動它。執行 systemd-cgls --unit two-children.service,確認三個 PID:shell 及其兩個子程序。使用 sudo kill <pid> 終止其中一個子程序。再次檢查 unit。程序樹少了一個程序,但狀態仍是 active (running),journal 也沒有新增訊息。現在改執行 sudo kill -9 <shell pid>。unit 會失敗,剩餘的子程序會被清理,因為 KillMode=control-group 是預設值,而 journal 會顯示 Scheduled restart job, restart counter is at 1.
完整的 Restart= 設定值,以及何時 on-failure 優於 always
Restart= 可使用 7 種設定值,區分這些設定值的關鍵在於何者算是正常結束。systemd 會將結束代碼 0、SuccessExitStatus= 中列出的任何代碼,以及 SIGHUP、SIGINT、SIGTERM 和 SIGPIPE 視為正常結束。其他情況都算是不正常結束,包括 SIGKILL 和 SIGSEGV。
no是預設值。單元不會自行重新啟動,因此沒有Restart=設定列的單元在第一次發生當機後就會停止,且維持停止狀態。on-success只會在正常結束後重新啟動。on-failure會在結束代碼非 0、不正常訊號、啟動或停止逾時,或 watchdog 逾時後重新啟動。on-abnormal會在不正常訊號、逾時或 watchdog 逾時後重新啟動,但不會因單純的非 0 結束代碼而重新啟動。on-abort只會在不正常訊號後重新啟動,也就是發生當機時。on-watchdog只會在WatchdogSec=逾時後重新啟動。always會在上述所有情況後重新啟動,包括狀態為 0 的正常結束。
對於長時間執行的 daemon,on-failure 是正確的預設值。它會在當機後重新啟動服務,但不會干預刻意執行的 exit 0。always 適用於因外部因素而正常結束的程式,例如遠端端點中斷連線時會傳回 0 的 tunnel client。使用 always 的代價是會掩蓋錯誤:服務啟動後讀取損壞的設定檔、記錄錯誤並以 0 結束時,會無限迴圈重新啟動,而唯一的跡象就是重新啟動計數持續增加。
SuccessExitStatus= 會改變正常與不正常結束之間的界線。Borg 會在發出警告時傳回 1,在發生錯誤時傳回 2,因此未設定 SuccessExitStatus=1 的備份單元每次略過無法讀取的檔案時,都會被標記為失敗。RestartPreventExitStatus= 會列出即使使用 always 也會阻止重新啟動的代碼。這是程式表示不應重新啟動的正確方式。RestartForceExitStatus= 的行為則相反。備份工作應放在由計時器驅動的 Type=oneshot 單元中,而不是放入重新啟動迴圈;可參考依排程執行工作的服務與計時器配對。
測試時請注意一點。直接使用 kill <pid> 終止服務會送出 SIGTERM,而 SIGTERM 在正常結束清單中,因此 Restart=on-failure 會正確地不執行任何動作,讓你誤以為設定檔損壞。請改用 kill -9 <pid> 或 systemctl kill -s SIGKILL myapp.service。另外也要記住,Restart= 的任何設定值都不會在 systemctl stop 後觸發,也不會在單元因 BindsTo= 或 PartOf= 相依性消失而停止時觸發。停止工作不代表發生失敗。
RestartSec 與 100 毫秒的預設值
RestartSec= 是 unit 停止後,systemd 再次啟動該 unit 前的等待時間,預設值為 100 毫秒。請確認實際載入的 unit 設定:
systemctl show -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst myapp.service未設定該值的 unit 會顯示 RestartUSec=100ms。這個預設值適合偶爾發生一次故障後即可恢復的服務。對於完全無法啟動的服務,這個值並不適用,因為服務會在半秒內重啟 5 次,正好觸發下一節說明的速率限制。凡是需要等待資料庫、掛載或網路路由的服務,都應設定 RestartSec=5s 或更長的時間。
截至 August 2026,systemd 254 及更新版本也提供 RestartSteps= 與 RestartMaxDelaySec=。這些設定會讓延遲從 RestartSec= 開始增加,並在指定次數的嘗試內逐步延長,直到達到上限。Ubuntu 24.04 隨附 systemd 255,支援這些設定。Debian 12 隨附 systemd 252,不支援這些設定。當相依服務可能長時間停機時,逐步增加延遲才是正確做法。
「start request repeated too quickly」的實際意義
這不是 systemd 任意放棄啟動服務的狀態,而是一個計數器。規則是:如果某個 unit 在 StartLimitIntervalSec= 內啟動超過 StartLimitBurst= 次,systemd 就會拒絕再次啟動,並將其設為 failed 狀態。預設值是在 10 秒內啟動 5 次。
journal 會顯示這個順序:
myapp.service: Scheduled restart job, restart counter is at 5.
myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'start-limit-hit'.
Failed to start myapp.service - My application.而 systemctl start 會直接提供已寫好的修正方式:
Job for myapp.service failed because start of the service was attempted too often. See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details. To force a start use "systemctl reset-failed myapp.service" followed by "systemctl start myapp.service" again.systemctl reset-failed myapp.service 會清除計數器與 failed 狀態。除此之外,沒有其他操作會清除它,因此單純執行 systemctl start 仍會持續遭到拒絕,直到你執行該指令為止。手動啟動也會計入限制,因此你在編輯設定檔時不耐久候而執行幾次 systemctl restart,即使完全沒有發生 crash,也可能觸發這項限制。
容易造成誤解的地方是:start-limit-hit 從不會說明服務為何失敗。它只表示服務快速且重複地失敗。真正原因位於其上方的 journal 訊息中。
這兩項設定都應放在 [Unit] 區段中。你可能會找到將它們放在 [Service] 的範例;較舊版本的 systemd 接受這種寫法,混淆也因此產生。請將它們寫在 [Unit] 中,然後使用 systemctl show 詢問 systemd 實際載入的設定,因為只有已載入的值會生效。
[Unit]
Description=My application
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10s這會讓該 unit 在 5 分鐘內嘗試 5 次,之後才放棄。StartLimitIntervalSec=0 會完全停用這項限制,但你應了解這項選擇的後果:永遠無法啟動的服務現在會無限重試,並在每次重試時寫入 journal。全機預設值位於 /etc/systemd/system.conf,設定名稱為 DefaultStartLimitIntervalSec= 與 DefaultStartLimitBurst=。
還有一項相鄰設定需要注意。StartLimitAction= 決定達到限制時的處理方式,接受的值包括 reboot、reboot-force 與 poweroff。預設值是 none,會使 unit 失敗,但不會影響整台機器。在遠端 VPS 上,poweroff 表示該主機會保持關機狀態,直到你開啟供應商的主控台。
修正方法一:每個 unit 只執行一個程序
這在幾乎所有情況下都是正確做法。如果必須執行兩個程式,請撰寫兩個 unit。如此一來,每個 unit 都有實際的主要程序、明確的結束狀態,以及各自的 restart policy。您也能取得分離的日誌、資源限制與重新啟動計數器;這正是凌晨 3 點進行故障排除時所需要的資訊。
請在 unit 檔案中表達 unit 之間的關係,不要透過 shell script 處理。
After=只會控制啟動順序,不會說明任何失敗行為。Requires=會讓另一個 unit 與目前 unit 一起啟動;如果明確停止另一個 unit,也會停止目前 unit。BindsTo=包含Requires=的功能,並加入您所需要的行為:無論另一個 unit 因任何原因停止,包括當機,目前 unit 都會停止。請搭配After=使用,否則順序未定義。PartOf=會將停止與重新啟動向下傳遞,因此systemctl restart myapp.target會套用到所有PartOf=該 unit 的 unit。Upholds=(systemd 249 及更新版本,因此適用於 Ubuntu 22.04 及更新版本)會讓指定的 unit 持續執行:如果該 unit 停止,systemd 會再次啟動它。這項行為同樣受所有服務適用的啟動速率限制約束。
某個 worker 必須永遠在其 API server 執行時才運作,而 systemd 會在 API 啟動時持續維持該 worker:
# /etc/systemd/system/myapp-api.service
[Unit]
Description=myapp API server
Wants=network-online.target
After=network-online.target
Upholds=myapp-worker.service
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp serve
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target# /etc/systemd/system/myapp-worker.service
[Unit]
Description=myapp background worker
BindsTo=myapp-api.service
After=myapp-api.service
StartLimitIntervalSec=120
StartLimitBurst=5
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp worker
Restart=on-failure
RestartSec=5sworker 沒有 [Install] 區段,也不會手動啟用。API unit 透過 Upholds= 將它納入,因此您只需執行 systemctl enable --now myapp-api.service。重新載入設定,並檢查 systemd 如何建立這兩個 unit:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/myapp-worker.service
systemctl list-dependencies myapp-api.servicesystemd-analyze verify 在檔案內容正確時完全不會輸出任何內容。任何輸出都表示有問題,通常是因為 systemd 不認得您寫在該區段中的某個索引鍵,或相依的 unit 不存在。
修正二:使用 Type=notify,讓 systemd 取得不只是 PID 的資訊
如果程式支援 systemd notification protocol,請使用它。透過 Type=notify,服務會在準備就緒時通知 systemd,讓啟動順序真正受到控制,而不是僅憑預期;服務也能傳送 MAINPID=,讓 systemd 指向實際需要管理的程序,而不是啟動器。
WatchdogSec= 是值得投入設定工作的部分。設定後,服務必須至少以該頻率透過 sd_notify(3) 傳送 WATCHDOG=1。訊息停止後,systemd 會以 SIGABRT 終止服務,並將其標記為失敗,因此 Restart=on-failure 或 Restart=on-watchdog 就能讓服務重新啟動。這是內建機制中唯一能重新啟動仍在執行但已卡住之程序的方法;任何結束狀態政策都無法偵測這種情況。
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/bin/myapp serve
WatchdogSec=30s
Restart=on-failure
RestartSec=5swatchdog 觸發時,journal 會先記錄 myapp.service: Watchdog timeout (limit 30s)!,接著記錄終止程序的訊息。如果 unit 反而一直停留在 activating (start),直到 TimeoutStartSec 用盡,表示 READY=1 從未送達:程式可能不支援此 protocol,或 NotifyAccess=main 拒絕來自子程序的通知;journal 會同時記錄兩個 PID。
對於提供 HTTP health endpoint、但不支援 sd_notify 的軟體,合理的選擇是建立小型 timer unit 探測該 endpoint,並呼叫 systemctl restart;或者交由容器 runtime 執行探測。Compose healthchecks 及其 restart 行為就是為此用途提供的。
修法三:僅在別無選擇時,於 unit 內執行 supervisor
有些軟體確實以程序套件形式提供,並由無法拆分的啟動器負責管理。此時可在 unit 內執行 supervisor,但必須接受其後果:systemd 監看 supervisor,supervisor 監看其他所有項目,而重新啟動政策會分散在兩個檔案中。
這種情況最常見於容器 runtime。docker compose 或 podman unit 正是此模式;每個容器的重新啟動政策寫在 Compose 檔案中,而 systemd unit 只負責讓 runtime 持續執行。如果你的架構屬於這種情況,請參閱 開機時啟動 Compose stack 的 unit,其中包含可正常運作的版本,也說明了為何在該情境中,搭配 RemainAfterExit=yes 使用 Type=oneshot 通常是正確做法。
cgroup 仍會發揮作用。supervisor 啟動的所有項目都會留在該 unit 的 cgroup 中,因此 MemoryMax=、CPUQuota= 及停止時的清理作業,仍會涵蓋整個程序樹。只有重新啟動決策會交由 supervisor 處理。
無論選用哪一種 supervisor,都不要未經評估就在外層 unit 設定 Restart=always,同時在其中設定積極的重新啟動政策。兩層各自具備退避機制的重新啟動邏輯,會讓服務持續反覆啟動數分鐘,且 journal 無法說明原因。
ExitType=cgroup 不代表「任一程序結束時重新啟動」
ExitType=(systemd 250 和更新版本,因此 Ubuntu 24.04 與 Debian 12 都支援)是人們搜尋這個問題時會找到的設定,但它的行為與名稱所暗示的相反。預設值 ExitType=main 表示主要程序結束時,服務就會被視為已停止。ExitType=cgroup 表示直到 cgroup 中的最後一個程序結束前,服務都會被視為仍在執行。
因此,ExitType=cgroup 會降低 unit 對單一程序結束的敏感度,而不是提高敏感度。若程式會 fork 出實際的 worker,接著結束父程序,且不寫入 PID 檔案,導致 Type=forking 找不到 daemon,這就是適合使用的設定。但對本文描述的失敗情況而言,這是錯誤的設定。
沒有任何 Restart= 值代表「cgroup 中任一程序結束時重新啟動 unit」。如果需要這種行為,就必須讓每個 unit 只執行一個程序。如果無法拆分程式,且你能控制 wrapper script,最接近的做法是 wait -n;它會在第一個子程序結束時立即返回:
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1現在,任一子程序結束都會使 wrapper 以非零狀態結束,因此 Restart=on-failure 會生效。這只是折衷方案,不是修正方法。兩個程式仍共用一個重新啟動計數器與一個日誌串流,也無法單獨重新啟動發生故障的那一部分。
如何檢查實際發生的狀況
依照以下順序執行 4 個命令。
systemctl status myapp.service
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Result -p ExecMainStatus myapp.service
journalctl -u myapp.service -b -o short-precisesystemctl status會在同一個畫面顯示狀態、主要 PID 及 cgroup 樹狀結構。正常的 unit 會顯示 Active: active (running),並在 Main PID:行列出預期的程序名稱。如果底部的樹狀結構列出無法識別的程序,或缺少應該存在的程序,答案已經很明確。
systemd-cgls --unit會在不截斷內容的情況下列印相同的樹狀結構。當 unit 管理的程序超過少數幾個時,這項功能就很重要。
systemctl show會提供機器可讀的資訊。NRestarts=是重新啟動計數器,也是判斷服務已重新啟動 40 次,還是自開機後持續執行的最快方法。Result=會保存最近一次失敗的原因:exit-code、signal、timeout、oom-kill、watchdog或 start-limit-hit。ExecMainStatus=是上一個主要程序的原始結束狀態。
journal 會保存事件順序。請搜尋以下 3 行:
myapp.service: Main process exited, code=exited, status=1/FAILURE
myapp.service: Failed with result 'exit-code'.
myapp.service: Scheduled restart job, restart counter is at 1.code=exited, status=N表示程式選擇傳回 N,因此問題出在程式或其設定。code=killed, signal=SEGV表示程式當機。code=killed, signal=TERM通常表示其他程序要求它停止,這不是失敗,也不會觸發 Restart=on-failure。code=dumped表示程式留下 core file;安裝 coredumpctl list後,systemd-coredump會顯示該檔案。
在多台機器上收集資料時,NRestarts是值得定期收集的數值。計數器每天上升的 unit,就是每天都在失敗,無論是否有人察覺。當管理的機器超過 2 或 3 台後,以一致方式在每台伺服器上執行同一個命令,就能將猜測轉為報告。
FAQ
為什麼 systemctl 顯示我的服務仍在執行,但程序已經結束?
systemd 會為每個服務單元追蹤一個程序,也就是主要程序,而 Restart= 只會讀取該程序的結束狀態。此單元啟動的其他程序都位於相同的 cgroup 中;單元停止時,systemd 會終止這些程序,但不會監看它們是否結束。執行 systemctl show -p MainPID myapp.service,並將數量與 systemd-cgls --unit myapp.service 比較。如果已結束的程序出現在樹狀結構中,但不是 MainPID,表示 systemd 的行為完全符合設計。修正方式是每個單元只執行一個程序,並在單元之間以 BindsTo= 和 Upholds= 明確定義關係。
「start request repeated too quickly」代表什麼?
這表示該單元在 StartLimitIntervalSec= 內啟動超過 StartLimitBurst= 次。預設值是在 10 秒內啟動 5 次,因此 systemd 停止繼續嘗試。這是速率限制,不會說明服務失敗的原因,因此請讀取前面的 journal 訊息。使用 systemctl reset-failed myapp.service 清除狀態,然後修正根本原因。如果服務需要等待某個啟動較慢的元件,請提高 RestartSec=,因為預設的 100 毫秒間隔會讓 5 次嘗試在不到 1 秒內全部耗盡。
應該使用 Restart=always 還是 Restart=on-failure?
大多數情況使用 on-failure。它會在程序崩潰、以非零狀態結束、逾時或 watchdog 觸發時重新啟動,但不會處理刻意的 exit 0。只有在程式因自身無法控制的原因正常結束時,才使用 always,例如對端中斷連線時,該客戶端會回傳 0。使用 always 的代價是:服務讀取損毀的設定、記錄一則錯誤後以 0 結束時,會無限循環;唯一可見的徵兆是 systemctl show 中的 NRestarts 持續增加。
為什麼手動終止程序不會觸發重新啟動?
因為 systemd 將 SIGHUP、SIGINT、SIGTERM 和 SIGPIPE 視為正常結束,而一般的 kill <pid> 會傳送 SIGTERM。在 Restart=on-failure 下,正常結束不算失敗,因此不會重新啟動;設定看似失效,其實並非如此。請使用 kill -9 <pid> 或 systemctl kill -s SIGKILL myapp.service 進行測試;這些指令會造成非正常終止,並觸發該政策。同一規則也說明了為什麼 systemctl stop 絕不會與你的重新啟動政策衝突。
StartLimitIntervalSec 和 StartLimitBurst 應放在哪裡?
放在 [Unit] 區段。較舊的資料與較舊版本的 systemd 會將它們放在 [Service],因此複製而來的範例可能彼此不一致。不要猜測你的版本會採用哪一個。執行 systemctl daemon-reload 後,使用 systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service 詢問 systemd 實際載入的設定,並以這些數值為準。systemd-analyze verify /etc/systemd/system/myapp.service 會找出 systemd 完全不認得的鍵;設定檔正確時則不會輸出任何內容。