VPS上的systemd定时任务没有执行,先不要把问题归结为“cron更可靠”或直接改成每分钟运行。应先确认.timer是否已启用、.service是否被正确触发,再检查日历表达式、时区、错过任务后的补偿、运行账户和脚本环境。定时器负责触发,服务单元负责执行,二者要分开验收。
先看定时器和服务分别处于什么状态
systemctl list-timers --all
systemctl status example.timer --no-pager
systemctl cat example.timer
systemctl status example.service --no-pager
journalctl -b -u example.timer -u example.service --no-pager把example替换成实际单元名。list-timers --all能看到上次触发和下次触发时间,未激活、触发时间为空或服务反复失败时,原因不同。日志中要区分“定时器没有触发”和“定时器触发了但服务退出”两类问题。
确认安装关系:启用的是timer,不是service
常见的单元组合如下:
/etc/systemd/system/example.timer
/etc/systemd/system/example.service示例配置:
[Unit]
Description=Run example job
[Timer]
OnCalendar=*-*-* 03:20:00
Persistent=true
Unit=example.service
[Install]
WantedBy=timers.target修改后先检查语法,再启用定时器:
sudo systemd-analyze verify /etc/systemd/system/example.timer /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl enable --now example.timer
systemctl is-enabled example.timer
systemctl is-active example.timer只执行systemctl start example.service不会创建下一次定时触发;只启用service也不会自动启用timer。若单元由软件包或面板生成,应先确认实际路径和drop-in,不要直接修改会被覆盖的文件。
用systemd-analyze验证Calendar表达式
“每天凌晨运行”可能受星期、时区和语法影响。先让systemd解析表达式:
systemd-analyze calendar '*-*-* 03:20:00'
systemd-analyze calendar 'Mon..Fri 09:00'
systemctl show example.timer -p OnCalendar -p NextElapseUSecRealtime -p LastTriggerUSec输出会给出规范化后的时间和下一次触发点。表达式中的日期、星期和秒数不要靠猜;如果使用OnUnitActiveSec或OnBootSec,它们是相对计时,不是固定的墙上时间。
时区是最常见的“没执行”误判
timedatectl
date -Is
systemctl show example.timer -p TimeUSec -p Timezone服务器常用UTC,而业务人员按北京时间查看日志。先明确任务使用的时区,再比较NextElapse、journal时间和业务记录。修改时区会影响所有依赖系统时间的任务、证书和日志,不应只为一条timer临时改全局设置。
Persistent只补偿错过的时间点,不是无限补跑
Persistent=true会在定时器离线期间错过触发后,于下一次启动时尝试补偿,但它不等于把离线期间的每一次任务全部补跑,也不保证服务一定成功。若任务有重复执行风险,应在服务内部使用幂等键、日期锁或数据库状态记录。
维护后重点看:
journalctl -u example.timer --since '2 hours ago' --no-pager
journalctl -u example.service --since '2 hours ago' --no-pager
systemctl show example.timer -p Persistent -p RandomizedDelayUSec -p AccuracyUSecRandomizedDelaySec和AccuracySec可能让实际执行时间偏离表达式,尤其是批量服务器同时上线时。它们是调度特性,不应被误判成任务丢失。
手动运行成功,定时器仍失败?比较运行环境
交互终端拥有的PATH、HOME、工作目录、凭据和代理变量,systemd服务默认不一定具备。服务单元应使用绝对路径、明确的工作目录和必要的EnvironmentFile:
[Service]
Type=oneshot
User=backup
WorkingDirectory=/srv/backup
ExecStart=/usr/local/bin/backup-job
EnvironmentFile=-/etc/example/backup.env
TimeoutStartSec=30min环境文件权限应只允许服务账户或root读取。不要把密码、访问令牌直接写进可读的单元文件、命令行或日志;需要访问外部存储时,应使用受限凭据并记录脱敏结果。
任务重叠和超时:定时器触发了,但新实例没有并行运行
如果上一次oneshot仍在执行,下一次触发可能被合并或跳过,具体行为取决于服务状态和单元关系。检查进程、退出码和执行时长:
systemctl show example.service -p ActiveState -p SubState -p Result -p ExecMainStatus
ps -ef | grep -E '[b]ackup-job'
journalctl -u example.service -o short-iso --no-pager对不能并发的任务,可在脚本中使用flock或数据库锁,并明确超时后的回滚和告警。不要用无限重试把一个卡死的服务变成永久占用。
改完后怎么做一次可复核的验证
- 先手动启动service,确认退出码和输出符合预期。
- 用临时的短周期timer验证触发链,再恢复正式日历表达式。
- 重启VPS,检查timer是否自动激活、下次触发时间是否存在。
- 等待或模拟一个明确的触发窗口,核对timer和service两份日志。
- 检查任务产物、文件权限、数据库记录和外部通知,而不是只看服务为inactive。
- 记录时区、单元版本、上次触发、失败原因和人工补偿动作。
如果定时任务用于备份,验证的不只是“命令跑过”,还应检查备份文件可读、恢复点和业务数据一致性,可参考VPS备份恢复后的完整性验收。若任务启动依赖挂载、数据库或网络,需结合systemd服务依赖与启动顺序排查确认依赖关系。
常见修复边界
- 定时器未启用:执行
enable --now并确认timers.target。 - 表达式错误:先用
systemd-analyze calendar校验,不靠手算。 - 时区不一致:统一记录UTC或明确本地时区,不随意修改全局时钟。
- 错过触发:评估
Persistent=true与任务幂等性,必要时人工补偿。 - 服务失败:修复账户、权限、环境、路径或依赖,别只增加触发频率。
- 任务重叠:加锁、超时和告警,避免并行写同一份数据。
结论
systemd timer不执行时,最短排查路径是:看list-timers确认触发点,读timer和service日志,验证Calendar与时区,再比较服务运行环境,最后用受控重启和明确触发窗口复核。把定时器和服务拆开观察,通常比反复重启或改成更短周期更快找到真正原因。






