systemd timer 配置与使用

本文说明如何创建、启停和排查 systemd 定时任务,并收录 lvbu 服务器上 zahui.fan 证书续期的实际配置。适用于使用 systemd 的 Linux;以下系统级配置命令以 root 身份执行。

1. 两个文件分别负责什么

一个定时任务通常由两个同名文件组成:

文件 用途
demo-job.service 定义要执行的命令、用户、工作目录、超时时间
demo-job.timer 定义执行时间,以及是否补跑错过的任务

例如:demo-job.timer 到时间后启动 demo-job.service,后者执行脚本,输出可以通过 journalctl 查看。同名 service 是默认目标,也可以在 timer 中用 Unit= 明确指定。官方说明

系统级自定义文件放在 /etc/systemd/system/。日常启用的是 .timer;需要立即执行一次时,启动 .service。

2. 从零配置一个定时任务

下面创建一个每天北京时间 03:00 输出当前时间的示例任务,名称为 demo-job。

2.1 创建脚本

1
2
3
4
5
6
7
cat > /usr/local/sbin/demo-job <<'EOF'
#!/bin/bash
set -euo pipefail
/usr/bin/date --iso-8601=seconds
EOF

chmod 755 /usr/local/sbin/demo-job

实际使用时,将脚本内容替换为备份、数据同步等操作。脚本中的失败需要以非零状态退出,systemd 才能识别任务失败。

2.2 创建 service

文件:/etc/systemd/system/demo-job.service

1
2
3
4
5
6
7
8
[Unit]
Description=Run the demo scheduled job

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/sbin/demo-job
TimeoutStartSec=10min

Type=oneshot 适合执行完就退出的任务。User= 是执行用户;系统级 service 未填写时默认使用 root。需要相对路径时,设置 WorkingDirectory=/实际目录;命令和文件路径尽量使用绝对路径。

执行完后 service 通常显示 inactive (dead),要结合退出状态和日志判断是否成功。周期任务通常不要设置 RemainAfterExit=yes,否则任务完成后仍保持 active,后续 timer 触发时不会再次执行。service 说明、执行环境说明

2.3 创建 timer

文件:/etc/systemd/system/demo-job.timer

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Run demo-job daily

[Timer]
OnCalendar=*-*-* 03:00:00 Asia/Shanghai
Persistent=true
Unit=demo-job.service

[Install]
WantedBy=timers.target

OnCalendar= 指定日历时间。Persistent=true 会在 timer 再次启动时检查是否错过了执行时间,必要时补跑一次;停机多天不会按天补跑多次。它只适用于日历定时器,也不表示“任务失败后自动重试”。timer 参数说明

2.4 校验、手动执行、启用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 检查配置文件
systemd-analyze verify /etc/systemd/system/demo-job.service /etc/systemd/system/demo-job.timer

# 让 systemd 重新读取文件
systemctl daemon-reload

# 立即执行一次,验证实际命令
systemctl start demo-job.service
journalctl -u demo-job.service -n 30 --no-pager

# 设置 timer 开机启用,并立即开始等待下一次触发
systemctl enable --now demo-job.timer

# 查看排期
systemctl list-timers --all demo-job.timer

enable 设置开机启用,start 启动当前任务;enable --now 同时完成两件事。启用 timer 后,通常等待下一次触发;开启 Persistent=true 且存在错过的时间点时,可能先补跑。systemctl 说明

3. 常用时间规则

OnCalendar= 的常见格式:

1
星期 年-月-日 时:分:秒 时区

星期可以省略,* 表示任意值。时区未填写时使用服务器本地时区;写明 Asia/Shanghai 可以固定为北京时间。时间语法说明

需求 OnCalendar 值
每天 03:15 *-*-* 03:15:00 Asia/Shanghai
每小时整点 *-*-* *:00:00 Asia/Shanghai
每 5 分钟,按钟表时间对齐 *-*-* *:00/5:00 Asia/Shanghai
每周一 03:00 Mon *-*-* 03:00:00 Asia/Shanghai
周一至周五 09:00 Mon..Fri *-*-* 09:00:00 Asia/Shanghai
每月 1 日 02:00 *-*-01 02:00:00 Asia/Shanghai

先预览规则对应的时间,再修改配置:

1
systemd-analyze calendar --iterations=5 '*-*-* 03:15:00 Asia/Shanghai'

这个命令预览日历规则,不包含 timer 的随机延迟;实际排期用 systemctl list-timers 查看。

按时间间隔执行

如果需要“开机 5 分钟后开始,上次任务结束 1 小时后再执行”,可以用以下内容替换示例 timer 的整个 [Timer] 段:

1
2
3
4
[Timer]
OnBootSec=5min
OnUnitInactiveSec=1h
Unit=demo-job.service

OnUnitActiveSec= 从上次启动计时,OnUnitInactiveSec= 从上次结束计时。它们与按整点匹配的 OnCalendar= 含义不同。不要无意中保留旧的时间规则:多条规则通常是各自到时间都可以触发。间隔参数说明

随机延迟与时间精度

1
RandomizedDelaySec=45m

它在基准时间之后增加 0~45 分钟的随机延迟,适合让多台机器错开执行。另一个参数 AccuracySec= 默认是 1 分钟,会允许一定的触发合并窗口;timer 不保证精确到秒。需要更小的合并窗口时,可设置 AccuracySec=1s。精度与延迟说明

4. 日常管理命令

将下面的 demo-job 替换为实际任务名称。

操作 命令
查看所有已加载 timer,包含未运行的 systemctl list-timers --all
查看本任务排期 systemctl list-timers --all demo-job.timer
查看 timer 状态 systemctl status demo-job.timer
查看是否开机启用 systemctl is-enabled demo-job.timer
查看文件及覆盖配置 systemctl cat demo-job.timer demo-job.service
立即执行一次 systemctl start demo-job.service
暂停定时触发 systemctl stop demo-job.timer
恢复定时触发 systemctl start demo-job.timer
取消开机启用并暂停 systemctl disable --now demo-job.timer
查看上次结果与退出码 systemctl show demo-job.service -p Result -p ExecMainStatus

停止 timer 不会终止已经开始执行的 service。如确实需要中止正在运行的任务,另行执行 systemctl stop demo-job.service。管理命令说明

查看日志:

1
2
3
4
5
6
7
8
# 最近 50 条日志
journalctl -u demo-job.service -n 50 --no-pager

# 今天的日志
journalctl -u demo-job.service --since today --no-pager

# 实时跟踪,Ctrl+C 退出查看
journalctl -u demo-job.service -f

service 的标准输出和错误输出通常由 journal 收集,可用 -u 按服务筛选。日志说明

5. 修改已有任务

例如把示例任务改成每天 04:00:

1
vi /etc/systemd/system/demo-job.timer

将对应行改为:

1
OnCalendar=*-*-* 04:00:00 Asia/Shanghai

然后执行:

1
2
3
4
systemd-analyze verify /etc/systemd/system/demo-job.service /etc/systemd/system/demo-job.timer
systemctl daemon-reload
systemctl restart demo-job.timer
systemctl list-timers --all demo-job.timer

daemon-reload 重新加载配置;重启 timer 后核对新的排期。只修改脚本内容,通常不需要 daemon-reload,下一次执行会读取新脚本。

也可以用 systemctl edit demo-job.timer 创建覆盖配置。覆盖已有日历规则时,先用空值清除,再写入新值:

1
2
3
[Timer]
OnCalendar=
OnCalendar=*-*-* 04:00:00 Asia/Shanghai

否则新规则会与原规则叠加。修改后重启 timer,并用 systemctl cat 核对原文件与覆盖文件。规则重置说明

6. 常见问题

timer 已启用,但任务没执行

先运行 systemctl status demo-job.timer,确认状态为 active (waiting),再用 systemctl list-timers --all demo-job.timer 查看 NEXT。只有 enabled 并不代表当前已启动。随后检查时区、随机延迟和 service 日志。

在 SSH 终端能运行,timer 执行却失败

systemd 不会自动读取交互式 shell 的 alias、虚拟环境激活状态等配置。检查绝对路径、WorkingDirectory=、执行用户与权限,所需变量通过 Environment= 或合适的凭据配置显式提供。执行环境说明

ExecStart= 不会自动解释 shell 的 &&、管道和重定向。复杂逻辑放入脚本,或明确调用 shell。例如 Nginx 检查与重载已经放在上面的独立脚本中。命令语法说明

执行失败后如何处理

1
2
journalctl -u demo-job.service -n 100 --no-pager
systemctl show demo-job.service -p Result -p ExecMainStatus

修复日志中对应的问题后,手动启动 service 验证。若遇到启动频率限制,修复后可执行 systemctl reset-failed demo-job.service 再启动;它只清除失败状态,不修复任务本身。

上一次任务还没结束,下一次时间到了

同一个 service 仍在运行时,timer 不会再启动一个并发实例,也不会为每个错过的时间点排队。任务可能运行很久时,可按需要使用 OnUnitInactiveSec=,从上次结束后开始计算间隔。触发行为说明

7. 本机帮助

不同发行版的 systemd 版本可能不同,排查时优先参考目标服务器上的手册:

1
2
3
4
5
6
systemctl --version
man systemd.timer
man systemd.service
man systemd.time
man systemctl
man journalctl