Do not rely on manually checking the n8n execution log.
Do not rely on manually checking the n8n execution log. Build a scheduled workflow that queries the n8n API for recent failed executions and sends a daily or hourly summary. This catches failures in workflows that do not have an error workflow configured (or where the error workflow itself failed).
Real-world example: A team's error notification workflow had a bug that prevented it from sending Slack alerts for 3 days. A health check workflow would have caught the accumulating failures.
// Code node: "Query n8n API for Failed Executions"
// Uses n8n's internal API (available on self-hosted instances)
const baseUrl = $env.N8N_HOST || 'http://localhost:5678';
const apiKey = $env.N8N_API_KEY; // Set in n8n settings
const response = await this.helpers.httpRequest({
method: 'GET',
url: `${baseUrl}/api/v1/executions`,
headers: {
'X-N8N-API-KEY': apiKey
},
qs: {
status: 'error',
limit: 100,
// Get executions from the last hour
startedAfter: new Date(Date.now() - 60 * 60 * 1000).toISOString()
}
});
const failedExecutions = response.data || [];
// Group by workflow
const byWorkflow = {};
for (const exec of failedExecutions) {
const wfName = exec.workflowData?.name || 'Unknown';
if (!byWorkflow[wfName]) {
byWorkflow[wfName] = { count: 0, executions: [] };
}
byWorkflow[wfName].count++;
byWorkflow[wfName].executions.push({
id: exec.id,
startedAt: exec.startedAt,
stoppedAt: exec.stoppedAt,
error: exec.data?.resultData?.error?.message || 'Unknown error'
});
}
return [{
json: {
totalFailures: failedExecutions.length,
byWorkflow,
checkTime: new Date().toISOString(),
period: 'last_hour'
}
}];
```text
Format and send the report:
```javascript
// Code node: "Format Health Report"
const data = $input.first().json;
if (data.totalFailures === 0) {
return [{
json: {
send: false,
message: 'All clear -- no failures in the last hour.'
}
}];
}
const lines = [`*n8n Health Check: ${data.totalFailures} failures in the last hour*\n`];
for (const [workflow, info] of Object.entries(data.byWorkflow)) {
lines.push(`*${workflow}*: ${info.count} failure(s)`);
// Show the most recent error
const latest = info.executions[0];
lines.push(` Latest error: ${latest.error}`);
lines.push('');
}
lines.push(`_Check time: ${data.checkTime}_`);
return [{
json: {
send: true,
message: lines.join('\n')
}
}];
```text
```yaml
# Schedule Trigger: Run every hour
# IF node: Only send if $json.send === true
# Slack node: Post to #n8n-monitoring channel
Schedule: Every hour at :00
Channel: #n8n-monitoring
Alert threshold: Any failures (customizable)
```text
This is your safety net for the safety net. Even if individual error workflows fail, this health check catches the failures.
**Related:** [Use "Pin Data" to Freeze Node Output](../testing-and-debugging/01-use-pin-data-to-freeze-node-output.md) | [Break Large Workflows into Sub-Workflows](../workflow-architecture/01-break-large-workflows-into-sub-workflows.md)
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.