systemd timer 替代 cron 的日历表达式配置
把 cron 任务迁移到 systemd timer,关键不是把五段 cron 表达式机械翻译成另一串字符,而是先把“执行什么”和“何时执行”拆开:.service 负责命令、账户、工作目录与失败状态,.timer 负责日历表达式、补跑、精度和错峰。这样任务既能被单独启动测试,也能通过 systemd 的状态与日志入口排查。
本文用一个每天 02:30 执行的备份脚本做最小示例。假设脚本位于 /usr/local/sbin/backup-app,由 backup 用户运行。正式配置前,请确认脚本无需交互输入,并且所有路径都使用绝对路径。
官方地址:https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html
日历语法:https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html
- 同名的
backup.timer默认触发backup.service,通常不必额外写Unit=。 OnCalendar=*-*-* 02:30:00表示每天本地时间 02:30。Persistent=true只对OnCalendar生效,用于补触发 timer 停用期间错过的日历任务。- 先用
systemd-analyze calendar校验,再启用 timer。
先理解为什么要拆成两个 unit
cron 的一行同时装着时间和命令;systemd 则把两者分成独立 unit。backup.service 可以被手动执行,用来检查权限、环境与返回码;backup.timer 只负责在日历条件满足时激活它。官方手册说明,如果 timer 未显式配置 Unit=,它会默认激活与自己同名、仅后缀不同的 service。
例如 backup.timer 自动对应 backup.service。如果 timer 叫 nightly-backup.timer,service 也应叫 nightly-backup.service;只有确实要触发不同名称的 unit 时,才在 [Timer] 中写 Unit=other.service。
写出最小可用的 service 与 timer
先创建 /etc/systemd/system/backup.service。Type=oneshot 适合运行后退出的批处理任务。不要依赖交互式 shell 的 PATH、别名或当前目录;若脚本需要固定工作目录,应明确写 WorkingDirectory=。
# /etc/systemd/system/backup.service [Unit] Description=Backup application data [Service] Type=oneshot User=backup Group=backup WorkingDirectory=/srv/app ExecStart=/usr/local/sbin/backup-app
再创建 /etc/systemd/system/backup.timer:
# /etc/systemd/system/backup.timer [Unit] Description=Run application backup every day at 02:30 [Timer] OnCalendar=*-*-* 02:30:00 Persistent=true AccuracySec=1min [Install] WantedBy=timers.target
OnCalendar 使用实时时钟和日历语法。未指定时区时,它按系统当前时区解释;需要固定时区时可以把 IANA 时区名写在表达式末尾,例如 *-*-* 02:30:00 Asia/Shanghai。不要在同一组服务器上有的机器依赖本地时区、有的机器写固定时区,否则同一 unit 的实际触发时刻会难以比较。

先用 systemd-analyze calendar 校验表达式
systemd-analyze calendar 会解析并标准化日历表达式,同时计算下一次触发时间。它比仅凭肉眼判断更可靠,尤其适合包含星期范围、列表和步进值的表达式。
# 校验每天 02:30 的表达式,并查看标准化结果与下一次触发。 systemd-analyze calendar '*-*-* 02:30:00' # 校验工作日 09:00。 systemd-analyze calendar 'Mon..Fri *-*-* 09:00:00' # 校验每小时从第 2 分钟开始、每 15 分钟一次的步进表达式。 systemd-analyze calendar '*:02/15'
第三个表达式会被补全为完整日期和秒字段。若真正想要每小时的第 0、15、30、45 分钟,可写 *:00/15 并以本机解析结果为准。systemd 还提供 daily、weekly、monthly 等简写,但在团队配置中写出完整时间通常更容易审核。
| 需求 | OnCalendar 示例 | 说明 |
|---|---|---|
| 每天 02:30 | *-*-* 02:30:00 | 日期字段全匹配 |
| 周一至周五 09:00 | Mon..Fri *-*-* 09:00:00 | 星期名称使用英文 |
| 每月 1 日 04:10 | *-*-01 04:10:00 | 日期固定为 01 |
| 每小时每 15 分钟 | *:00/15 | 先用 calendar 子命令确认标准化结果 |
| 每天上海时间 02:30 | *-*-* 02:30:00 Asia/Shanghai | 不随主机默认时区变化 |
Persistent、AccuracySec 与随机延迟不要混用概念
Persistent=true 会把 service 上一次由 timer 触发的时间保存到磁盘。timer 再次激活时,如果停用期间至少错过了一次计划,就立即补触发一次;它不会把错过的每个时间点逐个重放。该选项只对配置了 OnCalendar 的 timer 有效,默认值是 false。
AccuracySec 是触发精度窗口,默认值为 1 分钟。它允许 systemd 在指定时间之后的一段窗口内合并唤醒,以减少无谓的 CPU 唤醒;若任务必须尽量靠近给定时刻,可以缩小该值,例如 AccuracySec=1s。没必要为普通夜间任务设置极小值。
RandomizedDelaySec 则主动在 0 到指定值之间增加随机延迟,用于让同一时间配置的大量任务错峰。它的默认值是 0。比如多台机器都在凌晨上传备份,可以加入:
[Timer] OnCalendar=*-*-* 02:30:00 Persistent=true AccuracySec=1s RandomizedDelaySec=20min
这里先增加随机延迟,再受 AccuracySec 的合并窗口影响。前者是削峰,后者是允许合并唤醒,两者目标不同。若配置了随机延迟,Persistent=true 的补触发也仍会受该延迟约束。
加载、启用并查看下一次计划
配置写完后先让 manager 重新读取 unit,再启用 timer。通常只启用 .timer,不需要 enable 同名 oneshot service。
# 重新读取新增或修改的 unit 文件。 sudo systemctl daemon-reload # 立即启动 timer,并让它在后续开机时进入 timers.target。 sudo systemctl enable --now backup.timer # 查看该 timer 的最近触发与下一次计划。 systemctl list-timers --all backup.timer # 查看 timer 当前状态及最后一次调度信息。 systemctl status backup.timer
list-timers 中的 NEXT 是下一次触发时间,LAST 是最近一次触发时间;它们确认的是“计划是否存在”。任务本身是否成功,还要看 service 状态和日志。

把执行结果和调度结果分开排查
在等待真实触发时间之前,可以先手动启动 service。这不会修改 timer 的日历表达式,适合确认脚本权限、路径和退出码。
# 单独测试执行单元;失败时命令返回非零状态。 sudo systemctl start backup.service # 查看最近一次 service 状态。 systemctl status backup.service # 查看本次开机以来该任务的日志。 journalctl -u backup.service -b --no-pager # 查看 timer 自身的调度日志,不等同于脚本输出。 journalctl -u backup.timer -b --no-pager
若 backup.timer 显示 active 且有 NEXT,但 service 从未运行,先确认系统时间、时区与表达式;带 OnCalendar 的 timer 会自动排在 time-sync.target 之后,但缺少可靠实时时钟的设备仍应确保时钟同步服务真正完成校时。若 service 已被触发却失败,则回到 service 日志检查用户权限、工作目录、环境变量和脚本返回码。
从 cron 迁移时最容易踩的边界
| 现象 | 原因 | 处理 |
|---|---|---|
| timer 激活但任务找不到命令 | 脚本依赖交互式 shell 的 PATH | 在脚本和 ExecStart 中使用绝对路径,显式配置 Environment |
| 机器开机后立刻执行 | Persistent=true 发现停机期间错过计划 | 这是补跑语义;不需要补跑时设为 false |
| 不是严格在整点执行 | AccuracySec 默认允许 1 分钟窗口 | 按业务需要缩小,而非一律设成 1us |
| 多台机器同时压垮后端 | 所有 timer 使用相同计划且无错峰 | 使用 RandomizedDelaySec,必要时再研究稳定随机偏移 |
| 修改 unit 后没有变化 | 未执行 daemon-reload | 重新加载后 restart timer |
| 只看 timer 误判任务成功 | timer 只负责激活 service | 同时查看 backup.service 状态与 journal |
修改计划后的完整收尾
编辑已有 timer 后,不要重复 enable;重新加载并重启 timer 即可。再次运行 calendar 校验和 list-timers,可以同时覆盖“表达式能否解析”和“manager 是否已经采用新计划”两个问题。
# 修改 backup.timer 后重新加载并重启调度单元。 sudo systemctl daemon-reload sudo systemctl restart backup.timer # 重新检查 unit 内容和下一次计划。 systemctl cat backup.timer systemctl list-timers --all backup.timer
如果只是每天、每周、每月这类日历计划,OnCalendar 加同名 service 已能替代大多数 cron 用法;若需求是“开机后延迟多久”“上次任务完成后再等多久”,应改用 OnBootSec、OnUnitActiveSec 或 OnUnitInactiveSec 等单调计时器,而不是继续堆叠日历表达式。
照妖镜随机数工具怎么用?趣味决策与结果边界说明
- 上一篇
- 照妖镜随机数工具怎么用?趣味决策与结果边界说明
- 下一篇
- Go json/v2 与 json v1 迁移时的字段兼容清单
-
- 文章 · linux | 4小时前 | Linux · 内存管理 · Linux 内存限制 cgroup v2 memory.events
- cgroup v2 限制服务内存并观察回收事件
- 494浏览 收藏
-
- 文章 · linux | 1天前 | Linux · mount namespace unshare Linux挂载隔离
- mount namespace 隔离临时挂载的操作边界
- 462浏览 收藏
-
- 文章 · linux | 2天前 | Linux · 运维 · GNU tar 增量归档 listed-incremental exclude-from CACHEDIR.TAG
- tar 增量归档排除缓存目录的参数组合
- 324浏览 收藏
-
- 文章 · linux | 4天前 | Linux · 运维 · 日志排查 · journalctl 启动日志 Boot ID --list-boots systemd日志导出
- journalctl 按启动会话筛选并导出日志
- 369浏览 收藏
-
- 文章 · linux | 5天前 | Linux · Linux防火墙 nftables verdict map 多端口策略
- nftables verdict map 组织多端口策略的规则设计
- 475浏览 收藏
-
- 文章 · linux | 5天前 | 权限控制 · 服务管理 · systemd socket激活 systemd.socket ListenStream Accept
- systemd socket 激活服务的依赖与监听配置
- 229浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 318次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 375次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 372次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 337次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 163次使用
-
- 新能源汽车充电设施巡检记录的设置方法
- 2026-10-04 422浏览
-
- Golangcron定时器和定时任务的使用场景
- 2023-01-28 208浏览
-
- Go编写定时器与定时任务详解(附第三方库gocron用法)
- 2022-12-26 444浏览
-
- 详解golang执行Linuxshell命令完整场景下的使用方法
- 2023-01-07 426浏览
-
- go程序部署到linux上运行的实现方法
- 2023-01-02 387浏览

