Tips > Reliability & Performance

Monitor Execution History with a Scheduled Health Check Workflow

Do not rely on manually checking the n8n execution log.

TipAdvanced2 min read

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)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.