systemd で最小権限のサービスを作る:作成から障害切り分け、削除まで

よるほろぐ編集部

· 更新: 2026年7月10日 · 約 17 分

systemd は、Linux でプロセスを起動・監視し、起動順序とログをまとめて扱うサービスマネージャーです。バックグラウンドで動かしたいスクリプトを単に & で起動すると、ログイン終了、再起動、異常終了への対処を自分で抱えることになります。.service Unit にすると、起動時の有効化、終了状態の確認、ログの絞り込みを同じ操作系で行えます。

ここでは、60 秒ごとに時刻をログへ出すだけの itnote-heartbeat.service を作ります。ネットワークへ接続せず、ファイルも書き込まないため、Unit の作成から削除までを安全に試せます。動作中の本番サービスと同じ名前を使わないでください。

対象読者と前提

対象は、Linux のターミナルでファイルを編集でき、システム全体の Unit を管理するために必要なときだけ sudo を使える人です。この手順は PID 1 として systemd が動作しているホストを対象にします。コンテナ、WSL、chroot などでは systemctl が接続する systemd がない場合があるため、まず確認します。

ps -p 1 -o comm=
systemctl --version

最初のコマンドが systemd を表示し、後者が systemd のバージョンを表示すれば、この手順を進められます。表示が異なる環境では、ホスト側で実行するか、その環境固有のサービス起動方法を確認してください。

システム Unit は /etc/systemd/system/ に置きます。ここへ書くための sudo は管理作業に限定し、実際のプロセスは root で実行しません。ユーザーのログイン中だけ動けばよいツールなら、~/.config/systemd/user/ に置く user Unit を検討してください。本記事では起動時にも動かす system Unit を扱います。

最小の再現用サービスを作る

まず、実行ファイルを管理者だけが変更できる場所に置きます。sudo tee 以外の編集方法を使う場合も、内容、所有者、実行権限は同じにしてください。

sudo install -d -o root -g root -m 0755 /usr/local/libexec
sudo tee /usr/local/libexec/itnote-heartbeat >/dev/null <<'EOF'
#!/bin/sh
while :; do
  date -Is
  sleep 60
done
EOF
sudo chown root:root /usr/local/libexec/itnote-heartbeat
sudo chmod 0755 /usr/local/libexec/itnote-heartbeat

次に sudoedit /etc/systemd/system/itnote-heartbeat.service を実行し、次の内容を保存します。DynamicUser=yes により、systemd が実行時だけ使う非特権ユーザーを割り当てます。スクリプトは標準出力へ時刻を書く以外の権限を必要としません。

[Unit]
Description=it-note.net systemd practice heartbeat
StartLimitIntervalSec=60s
StartLimitBurst=3

[Service]
Type=exec
DynamicUser=yes
ExecStart=/usr/local/libexec/itnote-heartbeat
StandardOutput=journal
StandardError=journal
Restart=on-failure
RestartSec=5s
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
RestrictAddressFamilies=AF_UNIX

[Install]
WantedBy=multi-user.target

[Unit] は説明、依存関係、起動制限を書く場所

Description=systemctl status に表示する説明です。依存関係を追加するなら Wants=Requires=After=Before= をここに書きます。ただし After=順序だけ を指定し、相手の Unit を起動する指示ではありません。

StartLimitIntervalSec=StartLimitBurst=[Unit] の設定です。これはサービスのプロセス設定ではなく、systemd が起動要求をどの頻度まで受け付けるかという Unit 全体の制限だからです。[Service] に置くと意図した設定になりません。

[Service] は実行方法と最小権限を定義する

Type=exec は、実行ファイルの起動に失敗したときに起動要求を失敗として扱えるため、常駐プロセスの出発点として扱いやすい設定です。ExecStart= にはシェルの作業ディレクトリに依存しない絶対パスを書きます。

この例では DynamicUser=yes で root 常用を避けます。NoNewPrivileges=yes は実行中に追加の権限を得る経路を抑え、ProtectSystem=strictProtectHome=yesPrivateTmp=yes は不要なファイルシステムへのアクセスを狭めます。RestrictAddressFamilies=AF_UNIX はネットワーク通信を必要としないこのサンプルを UNIX ドメインソケットだけに制限します。外部 API や TCP ソケットを使う実サービスへ、この行をそのまま持ち込むのは誤りです。必要な通信と書き込み先を洗い出してから、必要なものだけ許可してください。

プロセスが状態を保存する必要が出た場合は、安易に ProtectSystem= を外さず、必要に応じて StateDirectory= など systemd が管理する書き込み先を検討します。権限を広げる前に、まず失敗したパスと用途をログで確かめるのが安全です。

[Install] は自動起動への接続を定義する

WantedBy=multi-user.target は、systemctl enable 時に作られるリンクの接続先です。multi-user.target は通常の非 GUI を含むマルチユーザー起動の到達点で、enable した Unit を起動時の依存関係へ加えます。[Install] の内容は、通常のサービス実行中に読み取られて起動条件になるわけではありません。

構文を確認してから読み込み、起動する

Unit ファイルを保存しただけでは、実行中の systemd は新しい定義を知りません。先に systemd-analyze verify で Unit を検査し、成功してから daemon-reload と起動を行います。

# 0 で終了し、設定エラーの診断が出ないことを確認する
sudo systemd-analyze verify /etc/systemd/system/itnote-heartbeat.service

# Unit ファイルの変更を systemd に読み込ませる
sudo systemctl daemon-reload

# 自動起動を有効にし、今すぐ起動する
sudo systemctl enable --now itnote-heartbeat.service

verify が失敗した場合は、次のコマンドへ進まず、行番号を含む診断を直します。daemon-reload は Unit 定義を再読込する操作であり、すでに動いているプロセスを再起動する操作ではありません。

起動後は、状態とログの両方で成功を確かめます。

sudo systemctl is-enabled --quiet itnote-heartbeat.service && echo "enabled"
sudo systemctl is-active --quiet itnote-heartbeat.service && echo "active"
sudo systemctl status --no-pager --full itnote-heartbeat.service
sudo journalctl --unit=itnote-heartbeat.service --boot --no-pager --lines=20

最初の二つがそれぞれ enabledactive を表示し、statusactive (running) を示し、最後のログに ISO 8601 形式の時刻が少なくとも一行あれば成功です。ログを継続して見るには、最後のコマンドに --follow を加えます。

journalctl で失敗を切り分ける

systemctl status は直近の状態と少量のログをまとめて読む入口です。原因を追うときは Unit と現在の起動に絞った journalctl を続けて実行します。

sudo systemctl status --no-pager --full itnote-heartbeat.service
sudo journalctl --unit=itnote-heartbeat.service --boot --no-pager --lines=100
sudo systemctl show --property=Result --property=ExecMainStatus \
  itnote-heartbeat.service
sudo systemctl cat itnote-heartbeat.service

journalctl --unit= は対象 Unit のログに絞り込みます。--boot を付けると今回の起動以降だけを読みます。systemctl cat では、ディスク上の本体と drop-in 設定を確認できます。Unit ファイルを編集した直後なら、daemon-reload を忘れていないかも同時に確認してください。

典型的には、status=203/EXEC のような実行エラーなら ExecStart= の絶対パス、シバン、実行権限を確認します。Permission denied なら、まず ProtectSystem=ProtectHome=、実行ユーザーが読む必要のあるファイルを見直します。原因が分からないまま保護設定をまとめて外すのではなく、必要なディレクトリだけを設計し直してください。

修正後の手順は、構文確認、再読込、失敗状態の解除、再起動です。

sudo systemd-analyze verify /etc/systemd/system/itnote-heartbeat.service
sudo systemctl daemon-reload
sudo systemctl reset-failed itnote-heartbeat.service
sudo systemctl restart itnote-heartbeat.service

reset-failed は原因を直すコマンドではありません。StartLimitBurst= に達した Unit の失敗状態と起動回数の記録を、修正後にリセットするために使います。

network.targetnetwork-online.target を混同しない

このサンプルはネットワークを使わないので、ネットワーク Target への依存を置いていません。After=network.target を書いても、IP アドレス、DNS、デフォルトルート、接続先 API の応答まで準備できたことは保証されません。network.target は通常、ネットワークを提供する側が引き込む受動的な Target であり、消費側が待機条件として使うものではありません。

起動時にリモートサービスへ接続しなければ開始できない Unit だけは、次のように network-online.target を引き込み、その後に順序付けます。

[Unit]
Wants=network-online.target
After=network-online.target

network-online.target はネットワーク管理実装が「十分に設定された」と判断するまで待つための、一度きりの起動時同期です。その判断基準はネットワーク管理実装に依存し、起動後の接続断を監視するものではありません。接続先の可用性が必要なアプリケーションでは、アプリケーション側にも再試行と適切な待機時間を実装します。

再起動を安全に制御する

Restart=on-failure は、異常終了時には再起動を試み、明示的な systemctl stop には追従しない方針です。RestartSec=5s は再試行の間隔を空けます。さらに本記事の [Unit]StartLimitIntervalSec=60sStartLimitBurst=3 は、60 秒間に 3 回を超える起動を要求したときの連続起動を抑えます。上限に達すると start-limit-hit が記録されることがあります。

Unit ファイルを更新した場合は daemon-reload の後に、アプリケーションが再読み込みを正式にサポートしているときだけ systemctl reload を使います。ExecReload= を定義していない一般的なサービスへ、推測で reload を送るべきではありません。再起動が必要なら、利用者への影響を確認したうえで restart を実行し、直後に is-active と Unit のログで結果を確認します。

後片付け

試験を終えたら、起動中のプロセスを停止して自動起動も外してから、作成した二つのファイルだけを削除します。汎用の journal ファイルを削除する必要はありません。

sudo systemctl disable --now itnote-heartbeat.service
sudo rm /etc/systemd/system/itnote-heartbeat.service
sudo rm /usr/local/libexec/itnote-heartbeat
sudo systemctl daemon-reload
sudo systemctl reset-failed itnote-heartbeat.service

最後に次を実行し、not enabled が表示されれば自動起動のリンクは残っていません。

if sudo systemctl is-enabled --quiet itnote-heartbeat.service; then
  echo "still enabled"
else
  echo "not enabled"
fi

systemd は Unit を小さく保つほど、依存関係、権限、障害時の挙動を読みやすくできます。まずはこのように副作用のないサービスで、作成、検査、起動、ログ確認、削除を一周させてから、実際のアプリケーションに必要な設定だけを追加してください。

関連する Linux 実践入門

出典

共有: X でシェア はてブ

編集・検証

よるほろぐ編集部

Linux・コンテナ・CLI・開発環境の実践ガイドを編集・検証します。

編集方針を読む