稼働状態、起動の可否、起動時の依存関係への登録を分けて考える。例はシステムの example.service。ユーザーサービスは --user を付け、別のマネージャーとして確認する。
状態から終了理由を調べる
一つのサービスを確認する
systemctl status example.service --no-pager
--no-pager は直接出力。Loaded の場所・エラーから Active と主プロセスへ読む。非 active では非ゼロ終了する場合があり、問い合わせ失敗とは限らない。
systemctl show example.service -p ActiveState -p SubState -p Result -p ExecMainStatus -p UnitFileState
ActiveState=failed と Result=exit-code はプロセスの失敗を示す。ExecMainStatus は主プロセスの値であり、起動段階によって ExecStartPre なども調べる。
journalctl -u example.service -b -n 80 --no-pager
-u は unit、-b は今回の起動、-n は行数。前回の起動は -b -1、追跡表示は -f を選ぶ。
個別診断と全体一覧を分ける
systemctl --failed
原因修正後、必要なら sudo systemctl reset-failed example.service で失敗状態と起動制限カウンターを消す。設定修復やアプリ起動は行わない。
systemctl list-units --all --type=service
ディスク上の unit ファイルと有効化状態は systemctl list-unit-files --type=service。ロード済み集合とファイル一覧は範囲が異なる。
| 観察 | 意味 | 次の確認 |
| active (running) | プロセスは稼働中。業務の正常性は別 | 待ち受けと実際のリクエスト |
| active (exited) | RemainAfterExit を使う oneshot など | Type、ExecStart、終了結果 |
| failed / exit-code | 失敗を記録。reset-failed は原因を直さない | journal と Result を確認 |
| not-found | このマネージャーには unit がない | 名前、配置先、--user の要否 |
稼働状態と有効化は別の軸
図を準備しています
稼働と自動起動は別の軸start/stop は稼働、enable/disable は起動 link を変更する。mask は以後の起動を拒否するが既存 process を止めない。
{"id":"ops-systemd-state","title":"稼働と自動起動は別の軸","summary":"start/stop は稼働、enable/disable は起動 link を変更する。mask は以後の起動を拒否するが既存 process を止めない。","height":600,"html":"<main><p class=\"quiet\" data-i18n=\"scope\"></p><div class=\"cards\"><section class=\"card \"><h3 data-i18n=\"runtime\"></h3><strong id=\"run\"></strong></section><section class=\"card \"><h3 data-i18n=\"boot\"></h3><strong id=\"boot\"></strong></section><section class=\"card \"><h3 data-i18n=\"gate\"></h3><strong id=\"gate\"></strong></section></div><div class=\"controls\"><button type=\"button\" data-action=\"start\" data-i18n=\"start\"></button><button type=\"button\" data-action=\"stop\" data-i18n=\"stop\"></button><button type=\"button\" data-action=\"enable\" data-i18n=\"enable\"></button><button type=\"button\" data-action=\"disable\" data-i18n=\"disable\"></button><button type=\"button\" data-action=\"mask\" data-i18n=\"mask\"></button><button type=\"button\" data-action=\"unmask\" data-i18n=\"unmask\"></button></div><div class=\"explain\"><div class=\"stable \"><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"blocked\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"enableBlocked\"></p><p id=\"message\" aria-live=\"polite\"></p></div></div></main>\n","css":"main {\n display: grid;\n gap: 18px;\n min-width: 0;\n}\np {\n overflow-wrap: anywhere;\n}\n.controls {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n}\n.presets {\n display: flex;\n flex-wrap: wrap;\n}\n.presets button {\n flex: 1 1 85px;\n font-size: 15px;\n}\n.cards {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n}\n.card {\n border: 1px solid var(--rule);\n border-radius: 10px;\n padding: 14px;\n min-width: 0;\n background: var(--surface);\n}\n.card h3 {\n font-size: 15px;\n color: var(--muted);\n margin-bottom: 8px;\n}\n.card strong {\n font-size: 23px;\n font-weight: 500;\n overflow-wrap: anywhere;\n}\n.code {\n font-family: var(--viz-font-mono);\n font-size: 15px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.stable {\n display: grid;\n min-width: 0;\n}\n.stable > * {\n grid-area: 1/1;\n margin: 0;\n}\n.stable > .reserve {\n visibility: hidden;\n pointer-events: none;\n}\n.explain {\n border-left: 3px solid var(--accent);\n padding: 12px 14px;\n background: var(--accent-soft);\n}\n.arrow {\n text-align: center;\n color: var(--accent);\n font-size: 25px;\n}\n.row {\n padding: 12px;\n border: 1px solid var(--rule);\n border-radius: 8px;\n min-width: 0;\n}\n.row.on {\n border-color: var(--accent);\n background: var(--accent-soft);\n}\n.row.off {\n color: var(--muted);\n}\n.bar {\n height: 18px;\n background: var(--surface);\n border: 1px solid var(--rule);\n border-radius: 5px;\n overflow: hidden;\n margin-top: 8px;\n}\n.bar > i {\n display: block;\n height: 100%;\n background: var(--accent);\n width: 0;\n transition: width 0.22s;\n}\n.checks {\n display: grid;\n gap: 8px;\n}\n.checks label {\n display: flex;\n gap: 10px;\n align-items: center;\n padding: 9px;\n border: 1px solid var(--rule);\n border-radius: 7px;\n font-size: 16px;\n}\n.checks input {\n width: 18px;\n height: 18px;\n accent-color: var(--accent);\n flex-shrink: 0;\n}\nlabel.range {\n display: block;\n font-size: 16px;\n}\nlabel.range output {\n float: right;\n font-variant-numeric: tabular-nums;\n}\n.pair {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n}\n.flow {\n display: grid;\n gap: 6px;\n}\n.quiet {\n font-size: 14px;\n color: var(--muted);\n}\n.badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 5px;\n background: var(--accent-soft);\n color: var(--accent);\n}\nbutton {\n overflow-wrap: anywhere;\n}\n.large {\n font-size: 32px;\n color: var(--accent);\n font-weight: 500;\n}\n.layers {\n display: grid;\n gap: 12px;\n}\n.layer {\n border-left: 3px solid var(--rule);\n padding-left: 12px;\n}\n.layer.on {\n border-color: var(--accent);\n}\n@media (max-width: 520px) {\n .cards,\n .pair {\n grid-template-columns: 1fr;\n }\n .card {\n padding: 12px;\n }\n .card strong {\n font-size: 22px;\n }\n}\n","js":"const $ = (s) => document.querySelector(s);\nconst $$ = (s) => [...document.querySelectorAll(s)];\nconst t = (k) => viz.t(k);\nconst put = (id, v) => ($(\"#\" + id).textContent = v);\nconst say = (k) => put(\"message\", t(k));\nconst state = (v) => ($(\"main\").dataset.state = JSON.stringify(v));\nconst press = (key, v) =>\n $$(\"[data-\" + key + \"]\").forEach((b) =>\n b.setAttribute(\"aria-pressed\", String(b.dataset[key] === v)),\n );\nlet run = false,\n enabled = false,\n masked = false;\nfunction draw() {\n put(\"run\", run ? \"active\" : \"inactive\");\n put(\"boot\", enabled ? \"enabled\" : \"disabled\");\n put(\"gate\", masked ? \"masked\" : \"unmasked\");\n state({ run, enabled, masked });\n}\n$$(\"[data-action]\").forEach(\n (b) =>\n (b.onclick = () => {\n const a = b.dataset.action;\n let k = \"ok\";\n if (a === \"start\") {\n if (masked) k = \"blocked\";\n else run = true;\n }\n if (a === \"stop\") run = false;\n if (a === \"enable\") {\n if (masked) k = \"enableBlocked\";\n else enabled = true;\n }\n if (a === \"disable\") enabled = false;\n if (a === \"mask\") masked = true;\n if (a === \"unmask\") masked = false;\n say(k);\n draw();\n }),\n);\nsay(\"ok\");\ndraw();\n","audio":false,"strings":{"blocked":"mask 中は start 拒否。unmask 後に起動できる。mask 自体は停止しない。","boot":"起動 link","disable":"disable","enable":"enable","enableBlocked":"mask 中の unit は直接 enable できない。先に unmask。既存 link は別管理。","gate":"起動 mask","mask":"mask","ok":"対応する状態が変化し、別の軸は変わらない。","runtime":"現在の process","scope":"通常 service、初期 inactive/disabled。socket・依存起動・process 障害を省略。自由な順で操作する。","start":"start","stop":"stop","unmask":"unmask"}}| 目的 | systemctl | 効果 |
| 今すぐ起動・停止 | start / stop | 現在の稼働状態を変更 |
| ターゲット経由の起動登録・解除 | enable / disable | [Install] のリンクを管理。単独では起動・停止しない |
| 両方を実行 | enable --now / disable --now | 登録と稼働の両方を変更 |
| 通常の起動要求を禁止 | mask / unmask | mask は /dev/null を指す。稼働中の停止は別 |
現在の実行状態だけを変える
sudo systemctl start example.service
systemctl is-active example.service と実リクエストで確認する。停止は別途 sudo systemctl stop example.service。起動時の有効化は解除されない。
target 経由の起動を設定する
sudo systemctl enable example.service
is-enabled で確認する。同時に起動するなら enable --now。解除だけなら disable、停止もするなら disable --now を選ぶ。
mask の前に起動経路を調べる
systemctl list-dependencies example.service --reverse
--reverse を外すと依存先を表示する。socket / timer も調べる。未ロード unit 全体の静的解析ではない。起動を禁止するときだけ sudo systemctl mask example.service、解除は unmask。is-enabled で確認する。
disabled でも手動、依存関係、socket、timer から起動できる。static は通常 [Install] 情報を持たない。ローカルの unit ファイルがあると mask が競合する場合があり、上書きせず確認する。
unit ファイルかアプリ設定か
| 変更 | 操作 | 検証 |
| unit / drop-in を手動編集 | daemon-reload | show で解析値を確認。新プロセス設定には再起動が必要 |
| アプリ自身の設定 | reload | reload 対応が必要。接続への影響はアプリ次第 |
| プロセスを作り直す | restart | 停止後に起動するため中断を考慮 |
全設定を読んでから上書きを追加する
systemctl cat example.service
元の値と場所を記録し、配布元管理のファイルを直接編集しない。cat はディスク上、show は管理器が読み込んだ値を示す。
sudo systemctl edit example.service
エディターで有効な領域に次の Service 断片を書いて保存する。取消は保存せず終了。戻す場合は今回の drop-in だけを復元・削除し、reload / restart で確認する。他の上書きは残す。
[Service]
Environment=LOG_LEVEL=debug
Restart=on-failure
RestartSec=5s
systemctl edit の保存成功後は通常 unit 定義が自動再読込される。手動編集では daemon-reload が必要。ExecStart の置換は空の代入で旧値を消してから新コマンドを指定し、実際のサービス引数を保持する。
手動編集後に検証・読込する
systemd-analyze verify /etc/systemd/system/example.service
cat で主 unit の実際の場所を確認する。edit しても /etc に完全な主ファイルがあるとは限らない。エラーを先に直す。アプリ独自設定の検証は別である。
sudo systemctl daemon-reload
サービスのプロセスは再作成しない。通常、systemctl edit の保存時には相当の処理が済んでいる。
systemctl show example.service -p ExecStart -p Environment -p Restart
正しいことを確認し、新プロセスが必要なら restart 後に status / journal と実リクエストを確認する。reload 対応のアプリ設定だけなら sudo systemctl reload example.service を選ぶ。両方を機械的に続けない。
ユーザー管理器の状態は別に確認する
systemctl --user status example.service --no-pager
通常は ~/.config/systemd/user/ に配置する。同じユーザーで edit / daemon-reload / enable --now を行い、journalctl --user -u example.service を読む。sudo で root 側へ対象を変えない。
ユーザーサービスは systemctl --user と journalctl --user を使う。未ログインでもユーザーマネージャーを動かす必要がある場合に loginctl enable-linger USER を検討する。個別サービスの enable とは別である。
タイマー:起動条件と実行結果
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=30s
[Install]
WantedBy=timers.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/example-task
実行するプログラムを用意してから timer を有効にする。Persistent は OnCalendar に適用され、停止中に逃した実行を再有効化時に一度補える。全回分の再実行ではない。OnBootSec、OnUnitActiveSec は単調時計に基づく。
日時式と対応 unit を検証する
systemd-analyze calendar daily
タイムゾーンと時刻を確認する。実際の OnCalendar 式に置き換えられる。systemd-analyze verify で timer と service の両方を検証し、実行ファイルも確認する。
sudo systemctl daemon-reload
sudo systemctl enable --now example.timer
Persistent による補完実行が起動後に起き得るため、繰り返し実行時の挙動を確認しておく。
systemctl list-timers --all
NEXT は予定、LAST は起動であり成功記録ではない。service の journal と Result / ExecMainStatus を確認する。調度停止は sudo systemctl disable --now example.timer。実行中の処理は別に扱う。
起動時間とログ容量を別々に調べる
systemd-analyze critical-chain example.service
journal の時刻と照合する。並列起動やサービス種別で時間の解釈は変わる。
journalctl --disk-usage
削除が必要なら sudo journalctl --rotate で現在分を閉じ、別途 --vacuum-time=7d で古いアーカイブを削除する。必要な記録は先に保存する。継続的な容量制御は journald の SystemMaxUse などで設定する。
timer の active は前回処理の成功を示さない。service の Result とログを確認する。journalctl の vacuum はアーカイブログが対象で、総使用量の上限ではない。blame の順位だけで起動の律速も判断できない。
systemctl · systemd.timer · journalctl