systemd とsystemctlの基本の
使い方
(.timer, .service)

  • systemdはLinux起動時にPID1として立ち上がり、ネットワークやサービスなどOS全体を管理します。
  • 設定はユニットファイルで行い、.serviceで常駐やワンショット実行、.timerで定期実行を制御します。
  • 操作はsystemctlコマンドで行い、ファイル変更後はdaemon-reloadが必須です。
  • ログはjournalctlで確認でき、ログアウト後も動かすにはloginctl enable-lingerが必要です。
systemctl --user daemon-reload               # 変更を反映(必須)
systemctl --user enable --now myapp.timer    # タイマーの場合の起動・登録
systemctl --user enable --now myapp.service  # 常駐サービスの起動・登録

journalctl --user -u myapp.service -n 30 --no-pager

systemctl --user disable --now myapp.timer # タイマーの停止・解除Code language: CSS (css)

関連記事

1. systemd とは

Linux を起動すると、カーネルが最初に立ち上げるプロセスがあります。
それが systemd です。
PID(プロセスID)が必ず1番になります。

systemd とは PID 1 カーネルが最初に起動 昔:init シンプルな起動管理 今:systemd サービス・タイマー管理 .service .timer .socket unit file

systemd はその後、ネットワーク、ログ、タイマー、各種サービスといった「OS が動くために必要なもの」を順番に起動していきます。
昔は init というシンプルなプログラムがこの役割を担っていましたが、現在の主要な Linux ディストリビューション(Ubuntu、Debian、Fedora など)はほぼ systemd に移行しています。

systemd が管理する設定ファイルをユニットファイルと呼びます。
.service.timer.socket などの拡張子を持ちます。

1.1. systemctl とは

systemd を操作するコマンドが systemctl です。
サービスの起動・停止・状態確認・自動起動の設定など、ほぼすべての操作をこれで行います。

systemctl status nginx          # 状態確認
systemctl start nginx           # 起動
systemctl stop nginx            # 停止
systemctl restart nginx         # 再起動
systemctl reload nginx          # 設定再読込(プロセスは落とさない)
systemctl enable nginx          # OS起動時に自動起動するよう登録
systemctl disable nginx         # 自動起動を解除
systemctl enable --now nginx    # 登録して今すぐ起動も同時に行うCode language: PHP (php)

1.2. システムモードとユーザーモード

systemctl には2つのモードがあります。

| モード | コマンド | 権限 | ユニットファイルの場所 |
|–|-||-|
| システム | systemctl | root | /etc/systemd/system/ |
| ユーザー | systemctl --user | ログインユーザー | ~/.config/systemd/user/ |

nginx や sshd のようにシステム全体で動くサービスはシステムモードです。
自分のスクリプトを自分の権限で動かすだけならユーザーモードで十分です。
以降はユーザーモードを前提にします。

1.3. journalctlでログを確認する

ログは journalctl で見ます。
systemd が管理するサービスのログはすべてここに集まります。

journalctl -u nginx             # nginx のログ全件
journalctl -u nginx -f          # リアルタイムで流す
journalctl -u nginx -n 50       # 最新50行
journalctl -u nginx --no-pager  # ページャーなしで出力(ログが切れない)Code language: PHP (php)

2. .service ファイルの書き方(~/.config/systemd/user/

ユニットファイルは ~/.config/systemd/user/ に置きます。

.service ファイル oneshot 実行して終わるタスク Type=oneshot ExecStart=~/scripts/run.sh WorkingDirectory=%h/app タイマーと組み合わせて使用 常駐サービス プロセスを起動し続ける Type=simple Restart=always RestartSec=2 落ちたら自動で再起動 ⚠ PATH に注意 ~/.bashrc は読まれない → Environment=PATH=… で明示する

2.1. 一度だけ実行するタスク(oneshot)

タイマーと組み合わせてバッチ処理をさせる場合の基本形です。

[Unit]
Description=My batch task

[Service]
Type=oneshot
ExecStart=%h/scripts/mytask.sh
WorkingDirectory=%h/myproject

%h はホームディレクトリに展開される変数です。
/home/username を直接書いても動きますが、%h にしておくとユーザー名が変わっても使い回せます。

Type=oneshot は「実行して終わる」タスクに使います。
スクリプトが終了すると systemd はそのサービスを完了扱いにします。

2.2. 常駐させるサービス(restart)

一方、ループして待ち受けるスクリプト・プログラムの場合は、Type=simple にします。
これは、プロセスが起動し続けるサービスに使います。

[Unit]
Description=My watch daemon

[Service]
Type=simple
ExecStart=%h/scripts/mywatch.sh
Restart=always
RestartSec=2
WorkingDirectory=%h/myproject

[Install]
WantedBy=default.targetCode language: JavaScript (javascript)

Restart=always を書くと、スクリプトが落ちたときに自動で再起動します。
RestartSec=2 は再起動までの待機秒数です。

2.3. 落とし穴:ログインシェルではない

systemd のサービスとして実行するスクリプトは、ユーザー権限で実行してはいるものの、ログインシェルで起動しているわけではありません。

どういうことかというと、

  • ~/.bashrc~/.profile が読まれない
  • PATH/usr/local/bin:/usr/bin:/bin 程度しかない
  • nvm、pyenv、rbenv などでインストールしたコマンドが見つからない

たとえば、PATHが通っていないと処理の途中でコマンドが見つからないことがあります。
すると、終了コード 127 で失敗してしまいます。
ログに status=127/n/a と出たらこれが原因です。

そこで、使いたいコマンドのパスをサービスファイルの「Environment」に直接書きます。

[Service]
Environment=PATH=/home/chii/.nvm/versions/node/v22.0.0/bin:/usr/local/bin:/usr/bin:/bin
Type=oneshot
ExecStart=%h/scripts/mytask.sh

たとえば、nvm のバージョンディレクトリは ls ~/.nvm/versions/node/ で確認できます。

3. .timer ファイルの書き方

タイマーを設定すると、cron の代わりに自動実行するサービスとして使えます。
.timer.service は同じファイル名(base name)にして、セットで動きます。

.timer ファイル セット構成 myapp.timer myapp.service 同名ファイルをセットで配置 ~/.config/systemd/user/ Persistent=true → 補完実行 OnCalendar 書式 *:0/5 5分おき hourly 毎時0分 daily 毎日0時 *-*-* 07:00:00 毎日7時 Mon *-*-* 09:00:00 毎週月曜9時 man systemd.time で詳細確認 定期実行には OnCalendar を使う OnUnitActiveSec は環境によって繰り返しが止まることがある

たとえば、myapp.timermyapp.service を呼び出します。

[Unit]
Description=Run myapp every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.targetCode language: JavaScript (javascript)

Persistent=true を付けると、マシンが止まっていた間の実行を再起動後に補完します。
cron にはない機能です。

3.1. OnCalendar の書式

書き方意味
*:0/55分おき
*:0/33分おき
hourly毎時0分
daily毎日0時
*-*-* 07:00:00毎日7時
Mon *-*-* 09:00:00毎週月曜9時

書式の詳細は man systemd.time で確認できます。

3.2. OnUnitActiveSec を使う場合の注意

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min

この書き方は環境によって active (elapsed) のまま繰り返しが起動しないことがあります。
定期実行には OnCalendar を使うほうが確実です。

というのも、OnUnitActiveSecは、前回実行から次の実行を予約します。
OS起動後でないと、OnBootSecが実行されないので、システムを稼働させたままだと動かないことがあるのです1

4. 有効化と起動(daemon-reload)

ファイルを作成・編集したあとは必ずこの順番で実行します。

有効化と起動 ① daemon-reload 変更を反映(必須) ② enable –now 登録+即起動 myapp.timer ③ 確認 list-timers status systemctl –user daemon-reload systemctl –user enable –now myapp.timer systemctl –user list-timers –all ログアウト後も動かす loginctl enable-linger $USER
systemctl --user daemon-reload               # 変更を反映(必須)
systemctl --user enable --now myapp.timer    # タイマーの場合
systemctl --user enable --now myapp.service  # 常駐サービスの場合Code language: PHP (php)

daemon-reload を忘れると古い設定のまま動きます。

4.1. 状態確認(systemctl status)

list-timersNEXT 列が - になっているとタイマーが待機状態ではありません。
ただし LAST に時刻が入っていれば過去に実行されているので、NEXT- でもログに実行記録があれば正常に動いていることがあります。

# タイマーの一覧(次回・最終実行時刻が出る)
systemctl --user list-timers --all

# サービスの状態
systemctl --user status myapp.service
systemctl --user status myapp.timer

# ログ
journalctl --user -u myapp.service -n 30 --no-pager
journalctl --user -u myapp.service -f        # リアルタイムCode language: PHP (php)

4.2. ログアウト後も動かし続ける

デフォルトではログアウトするとユーザーサービスも止まります。

loginctl enable-linger $USERCode language: PHP (php)

このコマンドを一度実行しておくと、ログアウト後もサービスが動き続けます。
常時バックグラウンド処理をさせたい場合に必要です。

5. トラブル対応の流れ

# 1. ログで終了コードとエラーを確認
journalctl --user -u myapp.service -n 30 --no-pager

# 2. よくある終了コードの意味
#    127  コマンドが見つからない(PATH の問題)
#    1    スクリプト内のエラー
#    126  実行権限がない(chmod +x が必要)

# 3. 構文チェック
systemd-analyze verify ~/.config/systemd/user/myapp.service

# 4. 実行権限の確認と付与
ls -la ~/scripts/mytask.sh
chmod +x ~/scripts/mytask.shCode language: PHP (php)
  1. 元の「OnUnitActiveSec」は前回実行からX後に次の回を実施するという設定。で、それだけだと、「最初の1回」を実行する指定がされてないのですね。 –Systemctl Timerが動かないなと思ったら #systemctl – Qiita