KernelRanch
返回文章列表

定时任务对照:cron 与 systemd timer 的写法差异、时区与夏令时坑、日志到底在哪

2026-11-10 #运维

选 cron 还是 systemd timer,取舍点不在语法,而在可观测性和时区语义。cron 简单、可移植,但日志依赖 syslog,时区跟着系统走;systemd timer 有 Persistent=true 补跑、有 journalctl -u 可查、有单调时钟与日历时钟两套语义。同一台机器上两者常常并存:/etc/cron.d 里躺着历史任务,新写的服务用 timer。

写法对照

项目cronsystemd timer
定义位置crontab -e / /etc/cron.d/<name> / /etc/crontab/etc/systemd/system/foo.timer + foo.service
时间表达式5 段:分 时 日 月 周OnCalendar=*-*-* 03:30:00
周期任务*/5 * * * *OnCalendar=*:0/5 或 OnUnitActiveSec=5min
开机后一段时间@reboot(只一次)OnBootSec=5min
补跑错过的执行不支持Persistent=true(仅对 OnCalendar= 生效)
精确度控制无AccuracySec=(默认 1min 窗口)
随机抖动需自己算RandomizedDelaySec= / RandomizedOffsetSec=
日志syslog / journal(实现相关)journalctl -u foo.service
加载配置改文件即生效(cron 每分钟扫描)systemctl daemon-reload + enable --now

同一个"每天 03:30 跑一次备份"的两种写法:

# cron:/etc/cron.d/backup(注意第 6 列是用户名)
30 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
AccuracySec=1min

[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

两条命令确认 timer 是否真的被排上:

systemctl list-timers --all            # NEXT / LAST / PASSED 三列
systemctl status backup.timer          # 下次触发时间与上次结果
journalctl -u backup.service --since today   # 这次执行到底发生了什么

时区:三个层面的差异

第一层,cron 用系统时区。crontab 里的时间按 /etc/localtime 解释,改时区等于改所有任务的触发时刻,而且没有提示。

第二层,CRON_TZ 不是通用能力。cronie 的 crontab(5) 明确写了 CRON_TZ 用来指定该 crontab 的时区;但 Debian 用的是 vixie cron 3.0pl1,本次在容器里查这个二进制:

$ dpkg -l cron | tail -1
ii  cron  3.0pl1-162  amd64  process scheduling daemon
$ grep -ac CRON_TZ /usr/sbin/cron
0

二进制里连这个字符串都没有,写 CRON_TZ=Asia/Tokyo 只会被当成一个普通环境变量设置,不改变触发时刻。跨发行版迁移定时任务时,这一条最容易出事:在 CentOS 上验证过的配置搬到 Debian 上,时间就偏了。

第三层,systemd timer 的 OnCalendar= 同样按本地时区解析,改时区后需要 daemon-reload 并重启 timer 才会重新计算。而 OnBootSec=、OnUnitActiveSec= 这类单调时钟表达式与时区无关,不受影响,也不受系统休眠影响(WakeSystem= 会换成另一种时钟)。

夏令时:缺失小时不执行,重复小时执行两次

crontab(5) 把这个行为写得很直白:夏令时切换时"不存在的时间"(跳过的那个小时)永远不会匹配,安排在这段时间的任务不会执行;"出现两次的时间"会让匹配的任务执行两次。

对运维的实际影响是两条:在切换日,凌晨 2:00–3:00 之间的任务可能被跳过或重复;数据库备份这类"一天只能跑一次"的任务,重复执行可能带来并发写同一份文件的问题。systemd timer 侧的对策是 Persistent=true(错过会补,但补跑时机取决于 RandomizedDelaySec)加任务自身的幂等;cron 侧只能靠任务脚本里的锁文件。

日志在哪

cron 通过 syslog 记录执行与错误,所以日志在 /var/log/syslog(Debian/Ubuntu)或 journal 里,取决于系统装了哪个 syslog 实现。本次容器里两条路径都不存在:

$ ls -l /var/log/syslog
ls: cannot access '/var/log/syslog': No such file or directory
$ ls -l /var/log/cron
ls: cannot access '/var/log/cron': No such file or directory

这也是 cron 排障最难受的地方:任务没跑,日志里什么都没有,只能靠"任务自己往文件里写一行"来证明它到底有没有被触发。建议所有 cron 任务的命令行都显式重定向到自己的日志文件,别依赖 syslog。

两者混用时的取舍

同一台机器上并存两种调度器并不冲突,但要划定边界,否则排查时会忘记某个任务到底归谁管。实践中的分法是:与系统服务生命周期相关的(备份、证书续期、日志清理)用 timer,因为能拿到 unit 状态、退出码和 journalctl;与业务脚本、一次性迁移、临时抓取相关的用 cron,因为改一行就生效,不需要 daemon-reload。

迁移 cron 到 timer 时最容易漏掉的三件事:/etc/cron.d 里的任务带用户名列(第 6 列),迁到 unit 时要改成 User=;cron 的 PATH 很短(通常是 /usr/bin:/bin),unit 里要显式写 ExecStart 的绝对路径或设 Environment=;cron 任务靠 MAILTO 发结果,timer 没有等价物,需要自己写日志或在 OnFailure= 里挂一个通知单元。

小结

新写的任务优先用 systemd timer:Persistent=true 能补跑、journalctl -u 能查、AccuracySec 与 RandomizedDelaySec 能把抖动控制住。维护历史 cron 任务时,重点确认三件事:时区是否依赖了 CRON_TZ(Debian 上不生效)、有没有落在夏令时切换窗口、以及日志重定向是否写全。

无论用哪种,任务脚本本身要做幂等和加锁。调度器只保证"到点调用",不保证"只调用一次",也不保证"一定调用"——补跑、重复触发、机器休眠都会打破这个假设。一个简单的锁文件配合 flock 就能挡住并发重复执行;再给脚本加一行开始/结束时间记录,事后就能区分"没被调用"和"调用了但失败"。

数据来源与检索时间

来源URL对应数据
man7.org · crontab(5)https://man7.org/linux/man-pages/man5/crontab.5.html5 段字段含义、CRON_TZ 的说明、DST 缺失/重复小时的执行行为、*/23 只在字段内生效、/etc/cron.d 需要第 6 列用户名、crontab 以换行结尾的要求
man7.org · systemd.timer(5)https://man7.org/linux/man-pages/man5/systemd.timer.5.htmlOnCalendar=/OnBootSec= 语义、Persistent= 只对 OnCalendar= 生效、AccuracySec= 默认 1min、RandomizedDelaySec= 与 RandomizedOffsetSec=、WakeSystem= 对单调时钟的影响
man7.org · journalctl(1)https://man7.org/linux/man-pages/man1/journalctl.1.htmljournalctl -u <unit> 的过滤语义与 --since 时间窗

检索时间:2026-11-10(Asia/Shanghai)。

核验说明

  • 真跑:容器内确认 cron 版本为 3.0pl1-162(Debian vixie cron),grep -ac CRON_TZ /usr/sbin/cron 返回 0;/var/log/syslog 与 /var/log/cron 均不存在(原始报错已摘录)。
  • 真跑(失败记录):三次尝试让 cron 实际触发任务均未成功——/etc/cron.d/tzjob 与 crontab - 安装的 root crontab 内容、权限(600 root:crontab)、spool 目录(1730)都正确,cron 进程常驻,但 14:06–14:14 之间 run.log 始终未被创建,其间还发现 Debian cron 不支持 -x 调试参数(invalid option -- 'x')。因此 cron 的实际触发时刻、DST 行为未在本机观测到,均依据 crontab(5) 记录。
  • 未运行:systemctl、journalctl 在 debian:12-slim 容器内不存在(command -v 无输出),systemd timer 相关命令全部未在本机执行,仅依据 systemd.timer(5) 与 journalctl(1) 描述。
  • 未验证:Persistent=true 的补跑行为、AccuracySec 对触发时间的实际偏移,需要真实 systemd 环境才能观测。