工作流定时不触发:5 个原因逐个排

凌晨三点的日报没发出来。打开画布,节点都在,触发器上写着「每天早上九点」,执行记录一条没有。你把时间改成两分钟后,盯着屏幕等到天亮,它还是没动。

这种情况我遇到不止一次,原因每次都不一样。有的是压根没发布,有的是时区差了一整天,还有一次是机器在定时那一刻正好在重启。下面按「谁在数时间」这条线拆五种,每种都给一条能自己动手查的证据。

适合已经能画出工作流、点过执行按钮的人。没装过的先看安装那篇,界面不熟的翻界面导览。我这台是 2.43.7,跑在 Debian 上,时区 Asia/Shanghai。

先搞清楚:定时这件事分了四层

「定时不触发」这句话本身太粗。它至少包含四个独立环节,任何一层断了,现象都一样:界面正常,什么也没发生。

环节 谁负责 断了的样子
有没有线上版本 发布动作 画布能跑,执行记录永远空的
谁在数时间 调度器(进程内存里的计时器,或数据库里的队列) 进程一停就断,断了不补
按什么时间数 时区 + cron 或间隔 时间对不上,差几小时或差一天
到点跑哪一版 被钉住的那个发布版本 参数改了不生效,跑的还是旧的

排错就按这四行从上往下走,比在画布上反复点执行快得多。

一、没发布,或者发的是老版本

这一条占了我见过的故障里一大半。2.x 把「保存」和「发布」拆成了两件事,你在画布上改的东西只是草稿,n8n 自己存,一秒到五秒落库,没有保存按钮。想让定时器认,得点右上角那个发布。

节点面板顶上一句话写得很直白,我原样抄下来:

This workflow will run on the schedule you define here once you publish it.
For testing, you can also trigger it manually: by going back to the canvas
and clicking 'execute workflow'

注意后半句。手动点「执行工作流程」能跑,是因为编辑器直接跑当前草稿,跟定时器走的是两条路。所以「我点了能跑啊」证明不了定时器是活的。

我自己造了一遍

建了个每 20 秒触发一次的工作流,发布,访问日志里立刻出现规律的一串:

09:30:40  09:31:00  09:31:20  09:31:40

然后把间隔从 20 秒改成 300 秒,只保存,不发布。接下来的五分钟里,日志照旧:

09:33:20  09:33:40  09:34:00  09:34:20  09:34:40

还是 20 秒一条。画布上那个数字明明写着 300。差别在服务端:改完草稿后,工作流的 versionId 换成了新的,activeVersionId 还指着旧的那个。线上跑的一直是被钉住的老版本。

n8n 画布上「每天早上九点」定时触发节点与后续节点,右上角是橙色的发布按钮
右上角那个橙色「发布」就是判据。它写着「发布」,说明这一版还没上线;变成「已发布」才轮到定时器管

所以看到「发布」两个字亮着,先别急着怀疑时区、cron、服务器。点一下,再看。

没发布的工作流,执行记录页是下面这样——左栏「未找到执行」,右边一片空白:

未发布工作流的执行页,左栏显示未找到执行,右侧空白
没发布就等于没有生产形态,定时器根本不会给它排班

还有一种更隐蔽的:发布了,但后来改了参数没重发。界面上一模一样,区别只是 activeVersionId 落后于 versionId。我建议改完参数就顺手点一次发布,别攒着。

二、时区不是你以为的那个

这条最坑人,因为它不报错,只是晚几个小时,或者干脆晚一天。

文档里写得很清楚:Schedule Trigger 依赖时区设置,优先级是「工作流自己的时区」压过「实例时区」;两个都没设,自托管实例默认 America/New_York。

纽约。不是 UTC,不是你机器的时区,是纽约。也就是说,一台什么都没配的服务器上,你写「每天早上 9 点」,实际会在北京时间 21:00 或 22:00 触发(夏令时切换前后差一小时)。很多人第二天发现报表是「昨天晚上」发的,就是这个。

我这台是特意设过的,GENERIC_TIMEZONE 和 TZ 都是 Asia/Shanghai。工作流设置里也能再单独覆盖一层:

n8n 工作流设置弹窗,时区为 Asia/Shanghai,下面是执行保存相关选项
工作流菜单 → 设置。第三行是时区,改完记得发布,不然跟上面那条一样不生效

怎么一眼看出来是时区问题

看执行记录里的时间,跟你写的时间差多少:

  • 差 12 或 13 小时 → 实例还在默认的纽约时区;
  • 差 8 小时 → 有人把它当 UTC 用了;
  • 差 1 小时 → 夏令时那几天,欧洲某些时区会这样;
  • 只差几分钟到几十分钟 → 不是时区,看下一条。

容器部署的人还多一层:宿主机的时区、容器的 TZ、n8n 的 GENERIC_TIMEZONE,三个可以互不相同。Docker 那篇里提过容器里的 Node 版本跟宿主机不是一回事,时区同理,别假设它跟着宿主机走。

三、时间规则本身写错了

n8n 的 cron 是六段,秒在最前面,第六段(星期)可以省。

秒  分  时  日  月  星期
0   30  9   *   *   *     每天 9:30:00
*/10 *  *   *   *   *     每 10 秒
0   0   1   *   *   *     每月 1 号 0 点

从 crontab guru 抄五段过来,位置整体错一位。你以为是「每小时的 30 分」,n8n 读成「每分钟的第 30 秒」。这种错不会报错,只会让你觉得它疯了一样地跑,或者一直不跑。

我拿 30 * * * * 试过一次:激活正常,三分钟内一次没触发。它被当成「每小时第 30 分」,下一次要等到 10:30。这一段我没蹲到整点,不下结论。照文档来:写六段,别省。

界面上的触发间隔有硬上限,填超了会被拦:秒和分都是 1–59,小时 1–23,天 1–31。所以「每 90 分钟」这种需求在界面上填不出来,只能切到 Custom (Cron) 写 0 */90 * * * * 之类(其实也不对,90 分钟没法用单个 cron 表达,得写两条规则或改用代码算)。

n8n Schedule Trigger 的 Trigger Interval 下拉菜单,包含 Seconds、Minutes、Hours、Days、Weeks、Months 和 Custom (Cron)
七种间隔,最后一项才是自己写 cron。前六项本质上都是帮你生成 cron

还有一个安静的死角:按月触发时填「31 号」,遇到 2 月、4 月、6 月这些月份,那个月一次都不会跑。文档原话是「If a month doesn't have this day, the node won't trigger」。月报、季度结算这类任务,填 28 号最省心。

另外,一个节点可以挂多条 Trigger Rules,它们是「并集」——每条规定的时间都会触发一次,不是后一条覆盖前一条。想表达「工作日 9 点 + 周末 11 点」,就挂两条,别指望一条写完。

四、重新发布改了计时起点

这条很少有人提,但它能解释一种奇怪现象:你改了间隔,它确实生效了,可时间点跟你算的不一样。

文档写得很直接:触发间隔随时可以改,但只在发布时更新;而且排期是从你发布的那一刻开始算的。原文举的例子是——原本每小时一次、应该在 12:00 跑,你在 11:30 改成两小时一次并发布,下一次是 13:30,也就是发布后两小时。

这解释了两类抱怨:

  • 「我改成每天跑一次,结果它在下午三点跑了」——因为你是下午三点发布的。
  • 「我设的每 6 小时,怎么时间每天都在漂」——因为起点是发布时刻,不是当天 0 点。

要精确到「每天 9:00」,别用「每 24 小时」,用 cron 0 0 9 * * *。间隔是相对时间,cron 是绝对时间,这两个混用最容易出这种事。

顺带说一个我实测到的细节:20 秒这种能整除 60 的间隔,触发点会落在整分钟的刻度上——09:30:40、09:31:00、09:31:20,毫秒偏移在 20–30ms 之间。所以看到执行记录整整齐齐排队,是正常的,不是假数据。

五、进程不在,停机期间的定时还不会补

前面四条都是配置问题,这条是运行环境问题。它最难查,因为你打开页面一切正常——服务已经起来了,只是中间那段时间的定时永远消失了。

我停了一次机器

一个每 20 秒跑一次的任务,我 systemctl stop 掉 n8n,等了 45 秒再起。执行记录里是这样的:

09:35:00  已成功
09:36:20  已成功     ← 中间三条没了
09:36:40  已成功

缺的是 09:35:20、09:35:40、09:36:00 三条。恢复后它没有补跑,直接从下一个刻度继续。系统起来要二十来秒,所以 09:36:00 那次也赶不上。

n8n 执行记录列表,时间从 09:36:20 直接跳到 09:35:00,中间有缺口
列表里这一跳就是停机窗口。不是漏记录,是真的没跑

为什么不补

我这台 2.43.7 的定时器跑在进程内存里。数据库里有张表记着每个触发器登记到了哪个调度器,trigger_kind 写的是 in-memory。内存里的计时器,进程一停就没了。文档的原话是「When an instance stops, its in-memory timers go with it. n8n skips any run whose time passed during the downtime rather than catching it up.」

2.45 之后换了做法,把待执行的排期先写进数据库,重启不丢,宽限期内的迟到还会补一次。补不补由节点设置里的 If Execution Is Missed 决定,三个选项:不跑、只补最近一次、每条规则各补最近一次。

这个选项在我的 2.43.7 上也有——节点「设置」页里能改,我截了图:

Schedule Trigger 节点设置页,含 If Execution Is Missed 与 Missed Execution Grace Period 两个选项
字段在,但得配合数据库调度器才生效。2.45 之前默认还是内存调度器,填了等于没填

所以我给一个不太受欢迎的建议:2.45 之前别指望补跑,把「漏跑」当成常态来设计。日报停了要有人知道,比事后补数据重要得多。告警那篇还没到,先在工作流末尾加个「失败通知」的错误工作流也行。

还有一种「不在」

队列模式(EXECUTIONS_MODE=queue)下,主进程只负责触发,真正干活的是 worker。worker 没起,定时器照常响,执行记录里会留着一堆一直没开始的条目,看起来像卡住了。查这个看 /rest/settings 里的 executionMode,我这台是 regular,不存在这层。生产上用队列模式的,先确认 worker 进程还活着。

怎么确认它真的在跑

翻执行记录是最直觉的办法,但有两个前提:这个工作流没关掉执行保存,而且你看的是「执行」标签而不是「评估」。生产实例上还有一种情况——执行记录被裁剪策略清掉了,你看到的是空的,任务其实跑得好好的。

所以我更信另一种证据:让任务在 n8n 之外留下痕迹。这次我用的是最土的办法——在工作流末尾接一个 HTTP Request,打自己实例的健康检查地址,带个标记参数:

GET https://n8n.fanooo.com/healthz/ping?tag=146demo

然后在服务器的 nginx 访问日志里按标记过滤:

grep 'tag=146demo' /var/log/nginx/n8n-ssl-access.log | tail -6
110.42.50.232 - - [06/Nov/2026:09:30:40 +0800] "GET /healthz/ping?tag=146demo" 200
110.42.50.232 - - [06/Nov/2026:09:31:00 +0800] "GET /healthz/ping?tag=146demo" 200
110.42.50.232 - - [06/Nov/2026:09:31:20 +0800] "GET /healthz/ping?tag=146demo" 200

一行一个时间,精确到秒,不受执行保存策略影响,也不受分页影响。停机那次缺口,就是从这串日志里先看出来的,回执行记录里一对,一模一样。

三种证据的用法:

证据 证明什么 什么时候不够用
执行记录里有 trigger 模式的条目 定时器响了,而且跑到了最后一个节点 保存被关、被裁剪时会为空
时间戳间隔是否规律 间隔参数有没有真的生效 只能看已经发生的,看不到下一次
外部打点日志 任务真的动过,且能对齐到秒 需要多一个请求,得自己清理
n8n 执行记录列表,每 20 秒一条已成功的执行
执行记录里每条都标着耗时和 ID。手动执行会写成 manual,定时触发会写 trigger,一眼能分开

验证完记得把临时工作流删掉。2.x 里删之前得先归档,不然接口会返回「must be archived」,我是先归档再删的。

几个真会撞上的报错

Invalid cron expression。切到 Custom (Cron) 之后表达式解析不了就报这个。文档给的排查法是:先对照官方示例的段数,再把你写的表达式去掉秒那一段,丢到 crontab guru 上验证。剩下五段能过,说明段数对了,问题在第一段。

发布按钮一直是灰的,鼠标停上去提示「此工作流没有需要发布的触发节点」。 只有一个 Manual Trigger 的工作流永远发不出去,它没有生产形态。加一个 Schedule Trigger 或者 Webhook 才亮。这个细节四个词那篇里写过。

用接口发布时报 path: ["versionId"], message: "Required"。 发布这个动作必须指明钉哪一版,接口不会替你猜。我是先 GET 一次拿到 versionId 再提交的。手工在界面上点按钮不会遇到。

执行记录里有一条耗时 5 秒多,其余都是几十毫秒。 那是我重启之后的第一条,进程冷启动还没热起来。不是任务卡住,第二三条就回到 50ms 上下了。

cron 里写了变量,改了没反应。 变量只在发布那一刻求值,之后改设置不会重排。要让它认新值,取消发布再发一次。

定时任务自己的风险

定时器这个东西,配错了不会报错,只会在某个不该跑的时间跑起来。三条具体的:

时区错位的代价不只是「晚了几小时」。凌晨跑批的任务如果落在白天,压的是数据库和上游接口;反过来,白天的任务挪到半夜,第二天早上没人看结果,出了问题要隔一天才发现。上线前把执行记录里的实际时间跟你写的时间对一遍,比读十遍配置管用。

停机漏跑必须人工补。n8n 不会告诉你「昨天少跑了一次」,下游拿着空数据算出来的报表看着是正常的。日切类任务建议在流程里带一个「上次成功时间」的检查,间隔超过预期就报警,而不是闷头跑。

打点用的 URL 别带 token 和密钥。访问日志是明文留档的,我这次只用了 tag= 一个参数,就是不想在日志里留下任何能用的东西。

20 秒一次是我为了几分钟内出结果才这么设的。真实业务里这个频率会把自己和上游一起压住,外部接口基本都会限流。十分钟一次已经算很勤了。

小结

定时不触发,先看右上角是不是还写着「发布」。写着,后面都不用查。

发布过了,就看执行记录里的时间和自己写的时间差多少。差十小时以上是时区,差几分钟是规则,一条都没有是进程或者调度器。

最后记住一件事:2.45 之前,停机期间的定时是直接丢掉的,没有补跑。设计的时候把漏跑当默认,比事后追数据省事。

接着往下

触发器不止定时一种,Webhook、轮询、应用事件各有各的坑,课程目录里按阶段排着:n8n 中文课程目录。

上一篇是 Workflow、Node、Connection、Execution:四个词讲清 n8n 的世界。启动报错和端口占用看 这篇。

参考资料

最后更新:2026 年 11 月 6 日,用的是 n8n 2.43.7。

THE END