the part nobody’s touched yet is the unrelated edit that switched it off. if the workflow definitions get exported to git on every save, “active: false” shows up as a one-line diff someone can catch, instead of half a day later in a stale report. the liveness check finds it after, the diff catches it at the edit.
That's a really good distinction. The liveness check helps detect the issue after it happens, whereas tracking workflow definitions in Git could catch an unintended change before it turns into an operational problem.
I haven't implemented workflow version tracking in this setup yet, but I can see how useful it would be, especially for detecting accidental changes to the active state.
Do you usually export workflows automatically on every save, or integrate that with a Git-based deployment process?
n8n has no on save hook, so a scheduled export to git from the CLI or API does the job, plus an alert whenever the diff touches the active flag. the enterprise source control feature works too, as long as edits go through git and not straight in the UI
A scheduled export with Git diff checks sounds like a practical way to catch accidental changes without adding another layer of complexity. I also like the idea of specifically flagging changes to the active state rather than reviewing every workflow change manually.
I'll consider adding this as a preventive layer alongside the liveness checker. Thanks for sharing the implementation details!
nice, the zero item check plus the active flag diff should cover most of the quiet ones. is the checker itself a workflow or a script calling the n8n API? if it’s a script, worth making sure that API call has a timeout, otherwise the watcher can hang silently too
Thanks! The checker is a separate Python script that calls the n8n API, not an n8n workflow. I wanted to keep the monitoring logic independent of the workflows being monitored.
And you're right about the timeout. The watcher shouldn't become another silent failure point itself. I'd include explicit request timeouts and handle API connection failures separately so they don't go unnoticed.
Would you also recommend an external watchdog to monitor the Python checker itself?
The detail that the error workflow only fires on failed executions is the whole lesson. An unpublished workflow creates no execution, so there is nothing to fail and nothing to alert on. Silence looked exactly like health.
The comments make the fix stronger. AgentSearch's per-workflow cadence table covers the case where runs happen but process zero items. Amorizz's advice to put the heartbeat on its own branch matters too, because otherwise an SMTP failure late in the flow also kills the signal that would have told you about it. Keying the watch list on workflow ID instead of name, as Viet suggested, avoids the rename problem.
At Ascendra Ventures we treat this as part of handover on automation builds. The client gets a list of every scheduled job with its expected interval, and the monitor checks for missing runs, not only failed ones.
Did you settle on 3x the interval for all workflows, or do the slow weekly ones need a different rule?
Thanks for bringing all those points together. I agree that a single threshold won't necessarily work for every workflow.
In my current implementation, I'm using a configurable freshness threshold based on roughly 3x the expected execution interval. I haven't yet implemented separate cadence rules for weekly or other less frequent workflows, so that's something worth improving.
How do you usually handle weekly workflows in your handover setup? Do you allow a larger grace window, or calculate the threshold based on the expected next execution time?
For weekly or irregular schedules I'd compute it from the cron expression rather than a multiple of the interval: take the last expected fire time before now, add a grace window (say 30 to 60 minutes for jobs that hit slow APIs), and alert if there's no execution started after that point. A 3x multiplier works for a 5-minute cron but means a weekly job could be three weeks late before anyone hears about it.
That's a very good point. I hadn't considered how much a 3x multiplier could delay alerts for weekly workflows. Calculating the next expected execution from the cron expression with a separate grace window sounds much more reliable.
I think this would be a worthwhile improvement to the current checker.
One question: would you also account for timezone changes and daylight saving time when calculating the next expected execution, especially for workflows scheduled across different regions?
This highlights an often-overlooked challenge in workflow automation: successful execution doesn't always mean successful business outcomes. At IT Path Solutions, we recognize the importance of building automation workflows with proper monitoring, output validation, and failure detection. A useful approach is to combine scheduled execution checks, business-level validation, and an external heartbeat so teams can detect both silent failures and incomplete results. Reliability should be measured by whether the intended outcome was achieved, not just whether the workflow ran.
Absolutely agree. A successful execution doesn't necessarily mean the expected outcome was achieved. That's another layer I'm interested in exploring beyond execution-level monitoring.
I'm curious how you approach business-level validation in practice. Do you usually build validation directly into each workflow, or keep it as a separate monitoring layer?
Your point that an error workflow needs an execution to exist before it can call it failed is the cleanest framing of this I've read. One gotcha with the heartbeat layer: if the ping is the last node after the email step, an SMTP failure stops the ping and you get paged for the wrong reason. I'd put it on its own branch right after the API reads and send the problem count in the body, like curl -fsS -m 10 --data-raw "problems=2" "$HEARTBEAT_URL". Then the outside service shows that the check ran and what it saw. Will the heartbeat fire from n8n itself, or from host cron?
That's a good catch. I hadn't considered the SMTP failure scenario causing a false alert at the heartbeat layer. Keeping the heartbeat on a separate branch after the API checks makes more sense.
In my current implementation, the monitoring logic runs separately using Python and the n8n API. The external heartbeat is something I've identified as the second layer, but I haven't tested it end to end yet.
I'm leaning towards keeping it independent of the email notification path so that an SMTP issue doesn't affect the heartbeat. Passing the problem count in the heartbeat payload is a useful addition too.
Would you recommend running that heartbeat through host cron rather than n8n itself, to keep the monitoring independent of the instance being monitored?
The distinction between “failed” and “never happened” is exactly the monitoring boundary many automations miss. I especially like that the watch checks the expected schedule, not just a global error stream.
I would add a correlation ID for the liveness checker’s own run and a small freshness budget per workflow: expected interval + normal jitter + dependency window. That makes the alert explainable (“last successful trigger was 37 minutes ago; budget is 15”) and avoids treating every workload like a one-minute cron.
That's a great suggestion. I particularly like the idea of having a freshness budget per workflow instead of relying on a fixed threshold. It would make alerts much more meaningful and easier to troubleshoot.
The current implementation keeps things relatively simple with a configurable freshness threshold. Adding dependency windows and more detailed alert context would be a good next improvement.
How do you usually determine the normal jitter and dependency window? Do you configure those manually for each workflow or derive them from execution history?
The "did it run?" flip is the clearest part of this for me. An error workflow needing an execution before it can fail is such a neat structural blind spot — it explains why a job can be broken for hours while every dashboard stays green.
The unrelated edit that switched the workflow off is also the most relatable cause here. I have seen the same pattern with scheduled jobs where a deploy or a restore quietly left the schedule inactive, and nothing failed because nothing started.
I do not run a separate did-it-run check at the moment, only error handling, so the external heartbeat point landed too. A watcher inside the same system cannot report when that system is down. The 3x interval rule is a useful starting point, though I would want a different grace window for a weekly job than for a 15-minute one.
Thanks for sharing your experience. That's exactly the kind of issue I wanted to highlight with this article. Everything can look healthy until you start checking whether the expected executions are actually happening.
I'd definitely recommend trying a separate liveness check alongside your existing error handling. Even a simple external check can catch things that internal error workflows cannot.
I'm curious, have you encountered this more often with scheduled jobs after deployments, or with workflows that get accidentally disabled during regular maintenance?
The line about the error workflow needing an execution to exist is the whole problem in one sentence. We had the same thing with a cron job that got commented out during a server move: nothing failed because nothing ran. Checking that the last successful run is recent, instead of asking whether anything errored, is the only version I really trust now.
Did you think about also alerting when a workflow flips to unpublished, or does the liveness check cover it well enough on its own?
That's a good point. In my current implementation, the liveness checker explicitly checks whether the workflow is active, so it detects an unpublished workflow during the next monitoring cycle. I haven't implemented a separate real-time alert for active-state changes yet.
I agree that having both would be stronger: detect the change immediately and keep the liveness check as a safety net.
Do you usually capture workflow state changes through n8n audit logs or a Git-based change-tracking mechanism?
Silent deadlocks in cron triggers will ruin your day faster than any explicit error. When your upstream proxies or scraper IPs drop offline and stall the queue, you need independent watchdog pings running outside the primary scheduler. For heavy multi-account workflows routing through isolated local ports, I pipe outbound requests through FNT vProxy Studio to handle rotating /64 IPv6 subnets and Android USB 4G tethering without paying per-gigabyte SaaS fees.
That's an interesting approach. I'd be interested in understanding more about your setup and how you're handling these scenarios in practice. Could you share some technical details and use cases over email at simplynadaf@gmail.com? Happy to take a closer look.
Head of AI & Process Transformation @ Postbank | RPA, GenAI & local-first AI | Building homelab-monitor & patzer | Find me on github.com/SikamikanikoBG & medium.com/@arsen.apostolov
the fix and the trap are the same object here. the check you built is the right shape, but it runs inside n8n, so when n8n goes down it goes silent too — your own blind-spot section says that, and i suspect it's the part most readers will skim past. the outside heartbeat is not an add-on, it's the actual fix; the internal check just makes the outside one cheap to run.
i ran into the same gap on my homelab a different way: a container that reported up but was serving stale data, and nothing was watching the watcher. i kept a small monitor that reads the machine's own state per process (github.com/SikamikanikoBG/homelab-monitor) mostly so i'd get paged when that watcher stopped reporting.
one question on the watch list: is max_quiet_minutes per workflow, or do you also want a global 'nothing ran at all' alarm?
You're right. The external heartbeat is not just an optional add-on; it's the missing layer that makes the monitoring approach reliable when n8n or the EC2 instance itself goes down. I called that out as a blind spot in the article, but haven't tested the external heartbeat end to end yet.
For the watch list, "max_quiet_minutes" is currently configured per workflow using a name-to-threshold mapping. I haven't implemented a separate global "nothing ran at all" alarm yet.
I think having both would be useful: individual checks for workflow-specific freshness and a global alarm for overall inactivity.
In your homelab setup, do you monitor individual processes independently and also maintain a machine-level heartbeat, or is everything consolidated into one watchdog?
I've seen similar issues with cron jobs and even my own scraping agents when a dependency failed silently. For n8n, I'd build a simple external liveness check that pings a health endpoint or checks a specific DB record to ensure the workflow actually ran, then alert on its absence. FNT vProxy Studio uses a similar heartbeat system for its multi-VPN tunnels and tethered Android 4G connections, ensuring my proxy farm is always alive before I even think about routing traffic.
That's the direction I'm exploring with the external heartbeat layer. Checking whether the monitoring process itself is alive is important, but independently verifying that the actual workflow completed is what makes the difference.
I like your suggestion of checking a specific DB record as well. It could provide another signal beyond the n8n execution history.
For your proxy setup, do you use the same heartbeat mechanism to detect connectivity issues and distinguish them from an application-level failure?
Software engineer with 20+ years of experience specializing in .NET / C# full-stack development and resilient infrastructure. Currently building Doyouok—a lightweight, agent-free heartbeat monitor bui
Great write-up. The distinction between a failed execution and a missing one is the important part.
For scheduled workflows, I’d also consider a heartbeat from the end of each watched workflow, alongside the heartbeat from the checker. That gives you an independent signal that the actual work reached its completion point. Unpublishing the workflow, stopping n8n or losing the server would all eventually show up as a missed check-in.
Disclosure: I build Doyouok, which has an n8n-verified community node for this. It can also evaluate a result condition locally, such as processedCount > 0, and report failure even when n8n considers the execution successful—where zero is genuinely unexpected. It’s free to start and available on n8n Cloud too.
Your API check adds useful context, such as whether the workflow is unpublished or its last execution failed. The external heartbeat answers a different question: did the expected completion signal arrive? I see the two as complementary.
Absolutely. I see the heartbeat and API check as complementary signals too. The API check tells us what happened inside n8n, while the external heartbeat confirms whether the expected workflow completion signal actually arrived. I also like the idea of evaluating result conditions locally, because a successful execution doesn't always mean the expected work was completed. Combining both gives us much better visibility.
Web search, Extract (page to markdown) and Render for AI agents. Pay per call in USDC via native x402 on Base. No account, no API key. Also on Pocket Network.
"It does nothing when an execution never starts" is the whole problem in one sentence. One thing that's worked for me alongside a liveness check: store an expected cadence per workflow (every 15 minutes, daily by 9 AM) outside n8n, so the check knows what "late" means for each one instead of using one global threshold. The other quiet case is a workflow that runs on time but its upstream source stopped returning items, which looks healthy to both an error workflow and a liveness check. Do you alert on runs that produced zero items too, or only on missing executions?
That's a really good point. I haven't implemented per-workflow expected cadence yet. In my current setup, I'm using a configurable freshness threshold based on the expected execution interval, so having an individual schedule for each workflow would definitely make the checks more accurate.
On zero-item executions, that's another scenario I haven't covered in this implementation. Right now, I'm checking whether executions happen and whether the last successful execution is within the expected window, not validating the actual output.
Curious how you handle that second case. Do you usually define a minimum expected item count for each workflow, or validate the output against some business-specific condition?
Web search, Extract (page to markdown) and Render for AI agents. Pay per call in USDC via native x402 on Base. No account, no API key. Also on Pocket Network.
A small table of workflow id plus expected interval works well here, so the checker reads each workflow's own cadence instead of one global threshold. For zero-item runs, flag a successful execution that returned nothing when that workflow normally returns items. That catches the quiet failures that still look green.
That makes sense. Keeping the expected interval in a separate configuration table is a simple approach and avoids adding unnecessary complexity to the monitoring workflow. I also like the idea of checking zero-item executions separately. I'll keep that in mind as a next improvement. The current implementation focuses on execution liveness and failure detection, but adding output validation would make it more useful for catching silent data issues.
The "check what ran, not what failed" framing is exactly right, and the 3x-interval rule for max_quiet_minutes is a sensible default.
Two gaps I've hit with this kind of watcher:
Matching on exact name means a harmless rename looks the same as a deletion. I key the watch list on workflow ID and keep the name only as a display label. Then "renamed" is just a label update, and "missing" really means deleted.
Looking at the latest execution's status misses runs that never finish. If the worker gets OOM-killed or the container restarts mid-run, the execution can sit in "running" indefinitely. It isn't "error", so the last-run check passes, and if it's recent enough the freshness check passes too. A fourth condition helps: any execution still running after a sensible ceiling for that workflow counts as a problem.
+1 on the external heartbeat being mandatory rather than optional. A watcher that lives inside the box it watches is the classic way to end up with a monitor that was dead for a week.
The "did it run?" flip is the whole lesson for me. An error alert only helps if something actually fails — a job that never starts looks exactly like a quiet day.
I've run into the same pattern outside code: a follow-up that was never sent doesn't bounce, it just sits there, and you only notice when the customer calls. The external heartbeat point lands too — a checker inside the same system can't tell you the system itself is down. Good, honest write-up, especially being clear about what you hadn't tested yet.
We're a place where coders share, stay up-to-date and grow their careers.
the part nobody’s touched yet is the unrelated edit that switched it off. if the workflow definitions get exported to git on every save, “active: false” shows up as a one-line diff someone can catch, instead of half a day later in a stale report. the liveness check finds it after, the diff catches it at the edit.
That's a really good distinction. The liveness check helps detect the issue after it happens, whereas tracking workflow definitions in Git could catch an unintended change before it turns into an operational problem.
I haven't implemented workflow version tracking in this setup yet, but I can see how useful it would be, especially for detecting accidental changes to the active state.
Do you usually export workflows automatically on every save, or integrate that with a Git-based deployment process?
n8n has no on save hook, so a scheduled export to git from the CLI or API does the job, plus an alert whenever the diff touches the active flag. the enterprise source control feature works too, as long as edits go through git and not straight in the UI
A scheduled export with Git diff checks sounds like a practical way to catch accidental changes without adding another layer of complexity. I also like the idea of specifically flagging changes to the active state rather than reviewing every workflow change manually.
I'll consider adding this as a preventive layer alongside the liveness checker. Thanks for sharing the implementation details!
nice, the zero item check plus the active flag diff should cover most of the quiet ones. is the checker itself a workflow or a script calling the n8n API? if it’s a script, worth making sure that API call has a timeout, otherwise the watcher can hang silently too
Thanks! The checker is a separate Python script that calls the n8n API, not an n8n workflow. I wanted to keep the monitoring logic independent of the workflows being monitored.
And you're right about the timeout. The watcher shouldn't become another silent failure point itself. I'd include explicit request timeouts and handle API connection failures separately so they don't go unnoticed.
Would you also recommend an external watchdog to monitor the Python checker itself?
The detail that the error workflow only fires on failed executions is the whole lesson. An unpublished workflow creates no execution, so there is nothing to fail and nothing to alert on. Silence looked exactly like health.
The comments make the fix stronger. AgentSearch's per-workflow cadence table covers the case where runs happen but process zero items. Amorizz's advice to put the heartbeat on its own branch matters too, because otherwise an SMTP failure late in the flow also kills the signal that would have told you about it. Keying the watch list on workflow ID instead of name, as Viet suggested, avoids the rename problem.
At Ascendra Ventures we treat this as part of handover on automation builds. The client gets a list of every scheduled job with its expected interval, and the monitor checks for missing runs, not only failed ones.
Did you settle on 3x the interval for all workflows, or do the slow weekly ones need a different rule?
Thanks for bringing all those points together. I agree that a single threshold won't necessarily work for every workflow.
In my current implementation, I'm using a configurable freshness threshold based on roughly 3x the expected execution interval. I haven't yet implemented separate cadence rules for weekly or other less frequent workflows, so that's something worth improving.
How do you usually handle weekly workflows in your handover setup? Do you allow a larger grace window, or calculate the threshold based on the expected next execution time?
For weekly or irregular schedules I'd compute it from the cron expression rather than a multiple of the interval: take the last expected fire time before now, add a grace window (say 30 to 60 minutes for jobs that hit slow APIs), and alert if there's no execution started after that point. A 3x multiplier works for a 5-minute cron but means a weekly job could be three weeks late before anyone hears about it.
That's a very good point. I hadn't considered how much a 3x multiplier could delay alerts for weekly workflows. Calculating the next expected execution from the cron expression with a separate grace window sounds much more reliable.
I think this would be a worthwhile improvement to the current checker.
One question: would you also account for timezone changes and daylight saving time when calculating the next expected execution, especially for workflows scheduled across different regions?
This highlights an often-overlooked challenge in workflow automation: successful execution doesn't always mean successful business outcomes. At IT Path Solutions, we recognize the importance of building automation workflows with proper monitoring, output validation, and failure detection. A useful approach is to combine scheduled execution checks, business-level validation, and an external heartbeat so teams can detect both silent failures and incomplete results. Reliability should be measured by whether the intended outcome was achieved, not just whether the workflow ran.
Absolutely agree. A successful execution doesn't necessarily mean the expected outcome was achieved. That's another layer I'm interested in exploring beyond execution-level monitoring.
I'm curious how you approach business-level validation in practice. Do you usually build validation directly into each workflow, or keep it as a separate monitoring layer?
Your point that an error workflow needs an execution to exist before it can call it failed is the cleanest framing of this I've read. One gotcha with the heartbeat layer: if the ping is the last node after the email step, an SMTP failure stops the ping and you get paged for the wrong reason. I'd put it on its own branch right after the API reads and send the problem count in the body, like
curl -fsS -m 10 --data-raw "problems=2" "$HEARTBEAT_URL". Then the outside service shows that the check ran and what it saw. Will the heartbeat fire from n8n itself, or from host cron?That's a good catch. I hadn't considered the SMTP failure scenario causing a false alert at the heartbeat layer. Keeping the heartbeat on a separate branch after the API checks makes more sense.
In my current implementation, the monitoring logic runs separately using Python and the n8n API. The external heartbeat is something I've identified as the second layer, but I haven't tested it end to end yet.
I'm leaning towards keeping it independent of the email notification path so that an SMTP issue doesn't affect the heartbeat. Passing the problem count in the heartbeat payload is a useful addition too.
Would you recommend running that heartbeat through host cron rather than n8n itself, to keep the monitoring independent of the instance being monitored?
The distinction between “failed” and “never happened” is exactly the monitoring boundary many automations miss. I especially like that the watch checks the expected schedule, not just a global error stream.
I would add a correlation ID for the liveness checker’s own run and a small freshness budget per workflow: expected interval + normal jitter + dependency window. That makes the alert explainable (“last successful trigger was 37 minutes ago; budget is 15”) and avoids treating every workload like a one-minute cron.
That's a great suggestion. I particularly like the idea of having a freshness budget per workflow instead of relying on a fixed threshold. It would make alerts much more meaningful and easier to troubleshoot.
The current implementation keeps things relatively simple with a configurable freshness threshold. Adding dependency windows and more detailed alert context would be a good next improvement.
How do you usually determine the normal jitter and dependency window? Do you configure those manually for each workflow or derive them from execution history?
The "did it run?" flip is the clearest part of this for me. An error workflow needing an execution before it can fail is such a neat structural blind spot — it explains why a job can be broken for hours while every dashboard stays green.
The unrelated edit that switched the workflow off is also the most relatable cause here. I have seen the same pattern with scheduled jobs where a deploy or a restore quietly left the schedule inactive, and nothing failed because nothing started.
I do not run a separate did-it-run check at the moment, only error handling, so the external heartbeat point landed too. A watcher inside the same system cannot report when that system is down. The 3x interval rule is a useful starting point, though I would want a different grace window for a weekly job than for a 15-minute one.
Thanks for sharing your experience. That's exactly the kind of issue I wanted to highlight with this article. Everything can look healthy until you start checking whether the expected executions are actually happening.
I'd definitely recommend trying a separate liveness check alongside your existing error handling. Even a simple external check can catch things that internal error workflows cannot.
I'm curious, have you encountered this more often with scheduled jobs after deployments, or with workflows that get accidentally disabled during regular maintenance?
The line about the error workflow needing an execution to exist is the whole problem in one sentence. We had the same thing with a cron job that got commented out during a server move: nothing failed because nothing ran. Checking that the last successful run is recent, instead of asking whether anything errored, is the only version I really trust now.
Did you think about also alerting when a workflow flips to unpublished, or does the liveness check cover it well enough on its own?
That's a good point. In my current implementation, the liveness checker explicitly checks whether the workflow is active, so it detects an unpublished workflow during the next monitoring cycle. I haven't implemented a separate real-time alert for active-state changes yet.
I agree that having both would be stronger: detect the change immediately and keep the liveness check as a safety net.
Do you usually capture workflow state changes through n8n audit logs or a Git-based change-tracking mechanism?
Silent deadlocks in cron triggers will ruin your day faster than any explicit error. When your upstream proxies or scraper IPs drop offline and stall the queue, you need independent watchdog pings running outside the primary scheduler. For heavy multi-account workflows routing through isolated local ports, I pipe outbound requests through FNT vProxy Studio to handle rotating /64 IPv6 subnets and Android USB 4G tethering without paying per-gigabyte SaaS fees.
That's an interesting approach. I'd be interested in understanding more about your setup and how you're handling these scenarios in practice. Could you share some technical details and use cases over email at simplynadaf@gmail.com? Happy to take a closer look.
the fix and the trap are the same object here. the check you built is the right shape, but it runs inside n8n, so when n8n goes down it goes silent too — your own blind-spot section says that, and i suspect it's the part most readers will skim past. the outside heartbeat is not an add-on, it's the actual fix; the internal check just makes the outside one cheap to run.
i ran into the same gap on my homelab a different way: a container that reported up but was serving stale data, and nothing was watching the watcher. i kept a small monitor that reads the machine's own state per process (github.com/SikamikanikoBG/homelab-monitor) mostly so i'd get paged when that watcher stopped reporting.
one question on the watch list: is max_quiet_minutes per workflow, or do you also want a global 'nothing ran at all' alarm?
You're right. The external heartbeat is not just an optional add-on; it's the missing layer that makes the monitoring approach reliable when n8n or the EC2 instance itself goes down. I called that out as a blind spot in the article, but haven't tested the external heartbeat end to end yet.
For the watch list, "max_quiet_minutes" is currently configured per workflow using a name-to-threshold mapping. I haven't implemented a separate global "nothing ran at all" alarm yet.
I think having both would be useful: individual checks for workflow-specific freshness and a global alarm for overall inactivity.
In your homelab setup, do you monitor individual processes independently and also maintain a machine-level heartbeat, or is everything consolidated into one watchdog?
I've seen similar issues with cron jobs and even my own scraping agents when a dependency failed silently. For n8n, I'd build a simple external liveness check that pings a health endpoint or checks a specific DB record to ensure the workflow actually ran, then alert on its absence. FNT vProxy Studio uses a similar heartbeat system for its multi-VPN tunnels and tethered Android 4G connections, ensuring my proxy farm is always alive before I even think about routing traffic.
That's the direction I'm exploring with the external heartbeat layer. Checking whether the monitoring process itself is alive is important, but independently verifying that the actual workflow completed is what makes the difference.
I like your suggestion of checking a specific DB record as well. It could provide another signal beyond the n8n execution history.
For your proxy setup, do you use the same heartbeat mechanism to detect connectivity issues and distinguish them from an application-level failure?
Great write-up. The distinction between a failed execution and a missing one is the important part.
For scheduled workflows, I’d also consider a heartbeat from the end of each watched workflow, alongside the heartbeat from the checker. That gives you an independent signal that the actual work reached its completion point. Unpublishing the workflow, stopping n8n or losing the server would all eventually show up as a missed check-in.
Disclosure: I build Doyouok, which has an n8n-verified community node for this. It can also evaluate a result condition locally, such as
processedCount > 0, and report failure even when n8n considers the execution successful—where zero is genuinely unexpected. It’s free to start and available on n8n Cloud too.Your API check adds useful context, such as whether the workflow is unpublished or its last execution failed. The external heartbeat answers a different question: did the expected completion signal arrive? I see the two as complementary.
Absolutely. I see the heartbeat and API check as complementary signals too. The API check tells us what happened inside n8n, while the external heartbeat confirms whether the expected workflow completion signal actually arrived. I also like the idea of evaluating result conditions locally, because a successful execution doesn't always mean the expected work was completed. Combining both gives us much better visibility.
"It does nothing when an execution never starts" is the whole problem in one sentence. One thing that's worked for me alongside a liveness check: store an expected cadence per workflow (every 15 minutes, daily by 9 AM) outside n8n, so the check knows what "late" means for each one instead of using one global threshold. The other quiet case is a workflow that runs on time but its upstream source stopped returning items, which looks healthy to both an error workflow and a liveness check. Do you alert on runs that produced zero items too, or only on missing executions?
That's a really good point. I haven't implemented per-workflow expected cadence yet. In my current setup, I'm using a configurable freshness threshold based on the expected execution interval, so having an individual schedule for each workflow would definitely make the checks more accurate.
On zero-item executions, that's another scenario I haven't covered in this implementation. Right now, I'm checking whether executions happen and whether the last successful execution is within the expected window, not validating the actual output.
Curious how you handle that second case. Do you usually define a minimum expected item count for each workflow, or validate the output against some business-specific condition?
A small table of workflow id plus expected interval works well here, so the checker reads each workflow's own cadence instead of one global threshold. For zero-item runs, flag a successful execution that returned nothing when that workflow normally returns items. That catches the quiet failures that still look green.
That makes sense. Keeping the expected interval in a separate configuration table is a simple approach and avoids adding unnecessary complexity to the monitoring workflow. I also like the idea of checking zero-item executions separately. I'll keep that in mind as a next improvement. The current implementation focuses on execution liveness and failure detection, but adding output validation would make it more useful for catching silent data issues.
The "check what ran, not what failed" framing is exactly right, and the 3x-interval rule for max_quiet_minutes is a sensible default.
Two gaps I've hit with this kind of watcher:
+1 on the external heartbeat being mandatory rather than optional. A watcher that lives inside the box it watches is the classic way to end up with a monitor that was dead for a week.
The "did it run?" flip is the whole lesson for me. An error alert only helps if something actually fails — a job that never starts looks exactly like a quiet day.
I've run into the same pattern outside code: a follow-up that was never sent doesn't bounce, it just sits there, and you only notice when the customer calls. The external heartbeat point lands too — a checker inside the same system can't tell you the system itself is down. Good, honest write-up, especially being clear about what you hadn't tested yet.