systemd.timer 入門

systemd.timer 入門

systemd.timer を初めて設定する時、「うーん。」これであっているんだろうかって思った時に読んでください。
Clock Icon2025.04.08

Linux で定期実行処理を行う際に、cron に慣れ親しんでいる方も多いのではないかと思います。
Amazon Linux 2023 で、コマンドのスケジューリングをしようと、いざ cron を使おうと思ったら、あれ?ない!?ってなったことないでしょうか。

Amazon Linux 2023 ではデフォルトで systemd.timer が利用でき、これによって定期実行を行うことができます。
私自身、systemd.timer の設定について慣れていなかったため、調べたことを備忘録として残そうと思います。

ちなみに Amazon Linux 2023 の標準リポジトリでも cronie をインストールすれば、cron を利用することも可能ではあります。
実際にインストール可能かだけ、確認しておこうと思います。

$ sudo dnf info cronie
Amazon Linux 2023 Kernel Livepatch repository   107 kB/s |  15 kB     00:00
Available Packages
Name         : cronie
Version      : 1.5.7
Release      : 1.amzn2023.0.2
Architecture : x86_64
Size         : 115 k
Source       : cronie-1.5.7-1.amzn2023.0.2.src.rpm
Repository   : amazonlinux
Summary      : Cron daemon for executing programs at set times
URL          : https://github.com/cronie-crond/cronie
License      : MIT and BSD and ISC and GPLv2+
Description  : Cronie contains the standard UNIX daemon crond that runs
             : specified programs at scheduled times and related tools. It is a
             : fork of the original vixie-cron and has security and
             : configuration enhancements like the ability to use pam and
             : SELinux.

でもここはぐっと我慢して、systemd.timer を使っていきたいと思います。

systemd.timer とは

systemd.timer.timer のファイル名拡張子と .service のファイル名拡張子の、2つの種類のファイルによって、スケジューリングおよび実行コマンドの定義を行います。
.timer には、主に定期実行のスケジューリングを記載し、.service には、主に実行するコマンドを記載します。
<ファイル名>.timer はデフォルトで同じ名前の <ファイル名>.service を参照します。

よって、何はともあれこの2つのファイルを用意する必要があるということです。

設定の流れ

設定の流れは以下の通りです。

  1. /etc/systemd/system 配下に、.service の拡張子を持つファイルを作成する
  2. /etc/systemd/system 配下に、.timer の拡張子を持つファイルを作成する
  3. 設定ファイルにエラーがないことを確認(systemd-analyze verify /etc/systemd/system/<作成したファイル>.*
  4. 現在のセッションでのタイマー起動(sudo systemctl start <作成したファイル>.timer
  5. 次回以降ブート時にタイマー起動(sudo systemctl enable <作成したファイル>.timer
  6. (設定変更時)ユニットファイルの設定変更
  7. 設定の反映(sudo systemctl daemon-reload

systemd timer の仕様を理解するにあたって、チュートリアル形式で分かりやすいページがあったので、このチュートリアルを参考に確認していきます。

systemdタイマの操作

.service ファイルの作成

(前提としてスケジュール実行したいスクリプトファイルをあらかじめ用意しておきます)

$ vi /home/ec2-user/helloworld.sh
#!/bin/bash
echo "$(date) Hello world!" >> "/home/ec2-user/test"
sleep 5

/etc/systemd/system 配下に .service ファイルを作成します。

$ sudo tee /etc/systemd/system/helloworld.service << EOF > /dev/null
[Unit]
Description="Hello World script"

[Service]
ExecStart=/bin/bash /home/ec2-user/helloworld.sh
EOF

[Service]セクションでよく指定されるオプション

オプション 説明
User コマンド実行ユーザー
ExecStart 実行コマンドを指定

User は、コマンド実行ユーザーを指定します。ここでは指定していないため、デフォルトのシステムルートで実行されます。後述で動作の違いを説明します。

.timer ファイルの作成

/etc/systemd/system 配下に .timer ファイルを作成します。

$ sudo tee /etc/systemd/system/helloworld.timer << EOF > /dev/null
[Unit]
Description="Run helloworld.service 5min after boot and every minute"

[Timer]
OnBootSec=5min
OnCalendar=*-*-* *:*:00
AccuracySec=100ms
Unit=helloworld.service

[Install]
WantedBy=multi-user.target
EOF

[Timer]セクションでよく指定されるオプション

オプション 説明
OnBootSecなど 単調タイマーのオプション※詳細は後述
OnCalendar リアルタイマーのオプション※詳細は後述
AccuracySec タイマーの精度
Unit 参照するサービスファイル

AccuracySecのデフォルト値は 1 minute (1分) の精度になっているため、1分以下のタイマー間隔は指定できません。1分未満の間隔でスケジューリングしたい場合は、AccuracySecを設定します。
Unitのデフォルト値は <ファイル名>.timer と同じ <ファイル名>.service を参照します。明示的に変更したい場合に設定します。
上記例の[Timer]セクションでは、いくつかのオプションを指定していますが、最小構成であればよく使う OnCalendar のみでも構いません。(スケジュールの指定方法は、後述の.timer ファイルのスケジュール指定方法を参照してください)

[Install]セクションでよく指定されるオプション

オプション 説明
WantedBy systemdが動作するターゲット

WantedBy=multi-user.targetWantedBy=timers.targetのどちらでも良いみたいですが、この項目は必須で記述する必要があるようです。

設定の確認

2つの設定ファイルが用意できたので、それぞれのファイルのシンタックスが正しいかチェックします。
例えば、.timer 設定ファイルのオプション名が OnCalnder のようにタイポしていた場合は、チェックができます。
(以下はシンタックスチェックが失敗しているケースです)

$ systemd-analyze verify /etc/systemd/system/helloworld.*
/etc/systemd/system/helloworld.timer:8: Unknown key name 'OnCalnder' in section 'Timer', ignoring.

出力結果が返ってこなければシンタックスチェックはOKです。

タイマー起動

現在のセッションでタイマーを起動します。

$ sudo systemctl start helloworld.timer

ステータスを確認します。

$ systemctl status helloworld.timer
● helloworld.timer - "Run helloworld.service 5min after boot and every minute"
     Loaded: loaded (/etc/systemd/system/helloworld.timer; disabled; preset: disabled)
     Active: active (waiting) since Mon 2025-04-07 23:15:47 UTC; 6s ago
    Trigger: Mon 2025-04-07 23:16:00 UTC; 6s left
   Triggers: ● helloworld.service

Apr 07 23:15:47 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.timer - "Run helloworld.service 5min after boot and every minute".

ブート時にも自動起動を行うよう設定します。

$ sudo systemctl enable helloworld.timer
Created symlink /etc/systemd/system/multi-user.target.wants/helloworld.timer → /etc/systemd/system/helloworld.timer.

設定できているか確認します。

$ systemctl list-timers
NEXT                        LEFT          LAST                        PASSED   UNIT                             ACTIVATES
Mon 2025-04-07 23:17:53 UTC 8s left       Mon 2025-04-07 23:16:53 UTC 51s ago  refresh-policy-routes@enX0.timer refresh-policy-routes@enX0.service
Mon 2025-04-07 23:18:00 UTC 14s left      Mon 2025-04-07 23:17:00 UTC 45s ago  helloworld.timer                 helloworld.service
Mon 2025-04-07 23:20:00 UTC 2min 14s left Mon 2025-04-07 23:10:31 UTC 7min ago sysstat-collect.timer            sysstat-collect.service
Mon 2025-04-07 23:59:09 UTC 41min left    -                           -        update-motd.timer                update-motd.service
Tue 2025-04-08 00:00:00 UTC 42min left    -                           -        logrotate.timer                  logrotate.service
Tue 2025-04-08 00:07:00 UTC 49min left    -                           -        sysstat-summary.timer            sysstat-summary.service
Tue 2025-04-08 01:01:53 UTC 1h 44min left Mon 2025-04-07 01:01:53 UTC 22h ago  systemd-tmpfiles-clean.timer     systemd-tmpfiles-clean.service
Mon 2025-04-14 01:34:39 UTC 6 days left   -                           -        fstrim.timer                     fstrim.service

8 timers listed.
Pass --all to see loaded but inactive timers, too.

NEXT -> Mon 2025-04-07 23:18:00 UTC 次の実行時間
LEFT -> 14s left あとどれだけの時間で実行するか(2秒後)
LAST -> Mon 2025-04-07 23:17:00 UTC 前回実行した時間
PASSED -> 45s ago 前回実行から経過した時間(2秒経過)
UNIT -> helloworld.timer タイマーのファイル名
ACTIVATES -> helloworld.service サービスのファイル名

実行ログは journalctl を通じて確認することができます。
-u で特定のユニット名のみに絞って確認、-f でテールモードで監視することができます。

$ journalctl -u helloworld -f
Apr 07 23:15:47 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:15:52 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:16:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:16:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:17:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:17:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:18:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:18:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:19:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:19:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.

1分間隔で実行できていることが確認できます。

ユニットファイルの設定変更

タイマーの間隔や、サービスの実行オプションなど設定を変更したくなった場合は、.timer.service の設定ファイルを直接編集します。

編集後には、設定ファイルのシンタックスチェックを再び行います。

$ systemd-analyze verify /etc/systemd/system/helloworld.*

## 出力がない場合は特にエラーなし

また、それぞれのファイルの中身を下記のコマンドで確認しておくと良いです。

$ systemctl cat helloworld
# Warning: helloworld.service changed on disk, the version systemd has loaded is outdated.
# This output shows the current version of the unit's original fragment and drop-in files.
# If fragments or drop-ins were added or removed, they are not properly reflected in this output.
# Run 'systemctl daemon-reload' to reload units.
# /etc/systemd/system/helloworld.service
[Unit]
Description="Hello World script"

[Service]
User=root
ExecStart=/bin/bash -c "date"
$ systemctl cat helloworld.timer
# Warning: helloworld.timer changed on disk, the version systemd has loaded is outdated.
# This output shows the current version of the unit's original fragment and drop-in files.
# If fragments or drop-ins were added or removed, they are not properly reflected in this output.
# Run 'systemctl daemon-reload' to reload units.
# /etc/systemd/system/helloworld.timer
[Unit]
Description="Run helloworld.service 5min after boot and every 24 hours relative to activation time"

[Timer]
AccuracySec=100ms
OnUnitActiveSec=5sec
Unit=helloworld.service
OnCalnder=aa

[Install]
WantedBy=multi-user.target

以下のコマンドを実行すると設定反映が行われ、タイマーが新しい設定内容に従って起動します。

$ sudo systemctl daemon-reload

その他、タイマーをストップする時

$ sudo systemctl stop helloworld

自動起動をオフにする時

$ sudo systemctl disable helloworld

こちらも覚えておきます。

詳細設定の補足

以下は、詳細な制御を行いたい時の設定の補足説明です。

.service ファイルの実行ユーザー権限

.service ファイルの User オプションによる動きを確認してみます。
まず最初に指定していない場合です。

$ systemctl cat helloworld.service
# /etc/systemd/system/helloworld.service
[Unit]
Description="Hello World script"

[Service]
ExecStart=/bin/bash /home/ec2-user/helloworld.sh

設定を反映します。

$ sudo systemctl daemon-reload

1分に一回スケジュールを実行、スクリプトは実行されてから5秒間待機するようになっているので、スケジューリングが開始したタイミングでプロセスの実行者が誰か確認してみます。

$ journalctl -u helloworld -f
Apr 07 23:25:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:25:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:26:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:26:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:27:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:27:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:28:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:28:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:29:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:29:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:30:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
^C

$ ps aux | grep helloworld.sh | egrep -v grep
root       80831  0.0  0.3 223008  3352 ?        Ss   23:29   0:00 /bin/bash /home/ec2-user/helloworld.sh

rootユーザーで実行されていることが確認できました。

次に、User=ec2-user を追記して、実行ユーザーを変更してみます。

$ systemctl cat helloworld.service
# Warning: helloworld.service changed on disk, the version systemd has loaded is outdated.
# This output shows the current version of the unit's original fragment and drop-in files.
# If fragments or drop-ins were added or removed, they are not properly reflected in this output.
# Run 'systemctl daemon-reload' to reload units.
# /etc/systemd/system/helloworld.service
[Unit]
Description="Hello World script"

[Service]
User=ec2-user
ExecStart=/bin/bash /home/ec2-user/helloworld.sh

設定を反映します。

$ sudo systemctl daemon-reload

同じように実行者を確認してみます。

$ journalctl -u helloworld -f
Apr 07 23:33:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:33:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:34:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:34:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:35:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:35:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:36:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:36:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:37:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 23:37:05 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 23:38:00 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
^C

$ ps aux | grep helloworld.sh | egrep -v grep
ec2-user   81310  0.0  0.3 222952  3192 ?        Ss   23:37   0:00 /bin/bash /home/ec2-user/helloworld.sh

ec2-user で実行できていることが確認できました。

.timer ファイルのスケジュール指定方法

Timer のスケジュールには、単調タイマー(Monotonic timers)リアルタイムタイマーがあります。
リアルタイムタイマーは実行する時間を指定します。

オプション 意味
OnCalendar 実行する日時
OnCalendar=<DayOfWeek> <Year-Month-Day> <Hour:Minute:Second>

<DayOfWeek>、指定できる値は、Sun、Mon、Tue、Wed、Thu、Fri、Satです。曜日を無視する場合は省略します。
<Year-Month-Day>、月と日を2桁で指定し、年を4桁で指定します。該当するあらゆる日時と一致するように、各値をワイルドカードで置き換えることができます。
<Hour:Minute:Second>、各値を2桁で指定します。該当するあらゆる日時と一致するように、各値をワイルドカード
で置き換えることができます。
(その他の詳細は以下のマニュアル参照)

systemd.time(7) - Linux manual page

設定は以下のように、月曜日から金曜日の10:00に実行、土曜日・日曜日の22:00に実行のように指定ができます。

$ sudo tee /etc/systemd/system/helloworld.timer << EOF > /dev/null
[Unit]
Description="Run helloworld.service"

[Timer]
OnCalendar=Mon..Fri *-*-* 10:00
OnCalendar=Sat,Sun *-*-* 22:00

[Install]
WantedBy=multi-user.target
EOF

その他、1分以下の間隔での指定も可能ですが、別途 AccuracySec=100ms のように、精度を細かく調整する必要があります。
(単調タイマーの定義の方法でも同じことです。)

## 精度を細かくする必要あり
AccuracySec=100ms

## 1分ごと
OnCalendar=*-*-* *:*:00

## 5秒毎
OnCalendar=*-*-* *:*:00/5

## 1秒ごと
OnCalendar=*-*-* *:*:*

1秒毎で試してみたところ速すぎて失敗してしまうようです。

$ journalctl -u helloworld -f
Apr 07 06:59:29 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:29 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42557]: Mon Apr  7 06:59:29 UTC 2025
Apr 07 06:59:29 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 06:59:30 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:30 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42558]: Mon Apr  7 06:59:30 UTC 2025
Apr 07 06:59:30 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 06:59:31 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:31 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42560]: Mon Apr  7 06:59:31 UTC 2025
Apr 07 06:59:31 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 06:59:32 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:32 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42561]: Mon Apr  7 06:59:32 UTC 2025
Apr 07 06:59:32 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 06:59:33 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:33 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:33 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:34 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:34 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:34 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:35 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:35 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:35 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:36 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:36 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:36 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:37 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:37 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:37 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:38 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Start request repeated too quickly.
Apr 07 06:59:38 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Failed with result 'start-limit-hit'.
Apr 07 06:59:38 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Failed to start helloworld.service - "Hello World script".
Apr 07 06:59:39 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:39 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42562]: Mon Apr  7 06:59:39 UTC 2025
Apr 07 06:59:39 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.
Apr 07 06:59:40 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: Started helloworld.service - "Hello World script".
Apr 07 06:59:40 ip-172-31-33-181.ap-northeast-1.compute.internal bash[42563]: Mon Apr  7 06:59:40 UTC 2025
Apr 07 06:59:40 ip-172-31-33-181.ap-northeast-1.compute.internal systemd[1]: helloworld.service: Deactivated successfully.

単調タイマーは、何らかのイベントからの経過時間を定義し、その経過時間に達した時にトリガーを引くことができます。

何らかのイベント
は以下の中から定義することができ、これらの定義からの経過時間をトリガーの条件として指定します。

オプション 意味
OnActiveSec タイマーサービスが最後にアクティブになった時間(.timer 起動時)
OnBootSec OS起動時間
OnStartupSec サービスマネージャー(Systemdのこと)が起動した時間
OnUnitActiveSec タイマーが参照するサービスが最後に起動した時間(.timer でサービスが呼び出された時)
OnUnitInactiveSec タイマーが参照するサービスが最後に停止した時間(.timer で呼び出されたサービスが停止した時)

使用できる単位は、usecmsecsecondsminuteshoursdaysweeksmonthsyearsです。
短縮系のminhdwなども使えます。(詳細は以下のマニュアル参照)

systemd.time(7) - Linux manual page

設定は以下のように、OS起動してから5分後に実行、その後24時間毎に実行というように指定できます。
OnUnitActiveSec は、繰り返し実行処理のための定義として使うことが多いですが、その場合は、OnActiveSecOnBootSecOnStartupSecなどを同時に記載しておく必要があります。
OnUnitActiveSec はサービスが起動した時間を起点にするので、最初の一回を起動する必要があるので注意が必要です。

$ sudo tee /etc/systemd/system/helloworld.timer << EOF > /dev/null
[Unit]
Description="Run helloworld.service"

[Timer]
OnBootSec=5min
OnUnitActiveSec=24h

[Install]
WantedBy=multi-user.target
EOF

スケジューリングのテスト

単調タイマーにしろ、リアルタイムタイマーにしろ、設定したいスケジューリング通りにコンフィグがかけているか心配になるときがあります。
そんな時は、設定しようとしている構文が実際どのようにして動作するかをあらかじめテストします。

OnCalendar=,Sun *-*-* 01,03:00:00 と書こうとしている時に、実際にいつのタイミングでトリガーされるかを確認します。

$ systemd-analyze calendar "Tue,Sun *-*-* 01,03:00:00" && date
Normalized form: Tue,Sun *-*-* 01,03:00:00
    Next elapse: Tue 2025-04-08 01:00:00 UTC
       From now: 59min left
Tue Apr  8 00:00:37 UTC 2025

現在、2025/4/8 00:00:37 Tue UTC で、2025/4/8 01:00:00 Tue UTC に実行されることが分かります。

5回分を確認することも可能です。
火曜日、日曜日の01:00:00と03:00:00に実行する定義となっているので、想定通りかと思います。

$ systemd-analyze calendar --iterations 5 "Tue,Sun *-*-* 01,03:00:00" && date
Normalized form: Tue,Sun *-*-* 01,03:00:00
    Next elapse: Tue 2025-04-08 01:00:00 UTC
       From now: 57min left
       Iter. #2: Tue 2025-04-08 03:00:00 UTC
       From now: 2h 57min left
       Iter. #3: Sun 2025-04-13 01:00:00 UTC
       From now: 5 days left
       Iter. #4: Sun 2025-04-13 03:00:00 UTC
       From now: 5 days left
       Iter. #5: Tue 2025-04-15 01:00:00 UTC
       From now: 1 week 0 days left
Tue Apr  8 00:02:59 UTC 2025

単調タイマーの場合は、systemd-analyze calendar の代わりに、systemd-analyze timespan を使います。

$ systemd-analyze timespan "15days 6h 32m"
Original: 15days 6h 32m
      μs: 1319520000000
   Human: 2w 1d 6h 32min

cron から置き換える時の読み替え

cron の設定から systemd.timer の設定に置き換える時のスケジューリングです。

Cron systemd timer
@reboot OnBootSec=1s
@yearly OnCalendar=*-01-01 00:00:00
@annually OnCalendar=*-01-01 00:00:00
@monthly OnCalendar=--01 00:00:00
@weekly OnCalendar=Sun --* 00:00:00
@daily OnCalendar=--* 00:00:00
@hourly OnCalendar=--* *:00:00

最後に

systemd.timer はユニットファイル(.service.timer)を作成する必要があるので、最初は慣れが必要なのですが、ポイントを押さえれば問題なく設定することができました。
シンタックスの確認コマンド(systemd-analyze verify)、設定状況の確認(systemctl catsystemctl list-timerssystemctl status)、ログの確認(journalctl -u <unitname> -f)など確認するためのコマンドが豊富なので、分かりやすいと感じました。
cron ではなく systemd.timer を利用しようと思った時に参考にしていただけると幸いです。

参考文献

systemdタイマの操作
第557回 systemdのユニットの関係を読む | gihyo.jp
systemd.special(7) - Linux manual page
I'm writing a systemd timer. What value should I use for WantedBy? - Unix & Linux Stack Exchange
systemd.timer(5) - Linux manual page
systemd.time(7) - Linux manual page
systemd.service(5) - Linux manual page

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.