n8n Timeout, Retry & Circuit Breaker Fault Tolerance Patterns
I build fault tolerance into n8n workflows because APIs fail. Services go down. Networks drop packets. If your workflow crashes every time an external service misbehaves, you’re not automating — you’re creating fragile pipelines. Here’s how I make workflows resilient.
I lost an entire day’s worth of order processing when a payment gateway had a 15-minute outage. No retries. No fallbacks. Just a broken workflow and angry customers. After that, I implemented timeout, retry, and circuit breaker patterns in every workflow that touches external services. The investment was two hours of setup. The savings have been thousands of dollars in recovered data.
Setting Timeouts
Timeouts prevent workflows from hanging indefinitely. When an API doesn’t respond within a reasonable time, the workflow should move on rather than wait forever. The HTTP Request node in n8n has a built-in timeout setting.
`
HTTP Request Node Configuration:
Timeout: 30000 ms (30 seconds)
Response Format: json
Send Headers: Yes
Authentication: API Key
`
Thirty seconds is a good default. For fast APIs (internal services, CDNs), use 5000ms (5 seconds). For slow APIs (reporting endpoints, batch processors), use 60000ms (60 seconds). The key is matching the timeout to the expected response time.
For workflows that call multiple services in sequence, set individual timeouts per request:
`javascript
// Code node: Enforce custom timeout logic
const startTime = Date.now();
const maxWait = 45000; // 45 seconds
// Before making HTTP call
if (Date.now() – startTime > maxWait) {
throw new Error(‘Workflow timeout exceeded’);
}
`
This pattern is useful when a workflow has a total time budget. If the first two API calls took 40 seconds, the third call only has 5 seconds left. The Code node enforces this constraint.
The n8n timeout and retry patterns article on timeout configuration covers advanced timing strategies.
Implementing Retry Logic
Retries handle transient failures. A 503 Service Unavailable might mean the server is temporarily overloaded. Retrying after a short delay often succeeds. A 429 Too Many Requests means you hit a rate limit. Retrying after the indicated delay is the correct response.
n8n’s HTTP Request node has a built-in retry setting:
`
HTTP Request Node:
Retry on Fail: Yes
Max Retries: 3
Retry Wait: 1000 ms (exponential backoff)
`
With exponential backoff, the wait time doubles after each retry: 1 second, 2 seconds, 4 seconds. This gives overloaded services time to recover without flooding them with immediate retries.
For more sophisticated retry logic, use a Code node with custom retry handling:
`javascript
// Custom retry with different behavior per status code
const statusCode = $json.status_code;
const retryableCodes = [429, 500, 502, 503, 504];
if (retryableCodes.includes(statusCode)) {
const backoffMs = Math.pow(2, $json.retry_count || 0) * 1000;
return [{
json: {
action: ‘retry’,
wait_ms: backoffMs,
reason: HTTP ${statusCode} - retryable error
}
}];
} else if ([400, 401, 403, 404].includes(statusCode)) {
return [{
json: {
action: ‘abort’,
reason: HTTP ${statusCode} - non-retryable error
}
}];
} else {
return [{ json: { action: ‘proceed’, data: $json.body } }];
}
`
This distinguishes between retryable and non-retryable errors. A 404 won’t be retried — it’s a permanent failure. A 503 will be retried with exponential backoff — it’s likely temporary. The n8n Code Node guide covers JavaScript execution in n8n for custom error handling.
Building a Circuit Breaker
A circuit breaker stops sending requests to a failing service entirely. After too many failures in a row, the circuit opens. No more requests until a recovery period passes. This prevents cascading failures and resource exhaustion.
The circuit breaker pattern has three states:
Implement this with a state table in your database:
`sql
CREATE TABLE circuit_breaker (
service_name VARCHAR(255) PRIMARY KEY,
failure_count INT DEFAULT 0,
last_failure TIMESTAMP,
state VARCHAR(20) DEFAULT ‘closed’,
opened_at TIMESTAMP,
success_count INT DEFAULT 0
);
`
The Code node manages state transitions:
`javascript
// Circuit breaker logic
const service = $json.service_name;
const now = new Date();
const failureThreshold = 5;
const recoveryTimeout = 60000; // 60 seconds
// Check current state
const state = $input.all()[0].json?.cb_state || ‘closed’;
if (state === ‘open’) {
const timeSinceOpen = now – new Date($input.all()[0].json?.opened_at || 0);
if (timeSinceOpen > recoveryTimeout) {
// Transition to half-open
return [{ json: { cb_state: ‘half_open’, action: ‘test_request’ } }];
} else {
// Still open – block request
return [{ json: { cb_state: ‘open’, action: ‘blocked’, wait_ms: recoveryTimeout – timeSinceOpen } }];
}
}
if (state === ‘half_open’) {
// Allow one test request
return [{ json: { cb_state: ‘half_open’, action: ‘test_request’ } }];
}
// Closed state – allow request, track failures
return [{ json: { cb_state: ‘closed’, action: ‘proceed’ } }];
`
After each request, update the circuit breaker state:
`javascript
// Update circuit breaker after response
const wasOpen = $input.all()[0].json?.prev_state === ‘open’;
const isSuccess = $json.status_code < 500;
let newState = $input.all()[0].json?.cb_state || ‘closed’;
let newFailures = parseInt($input.all()[0].json?.failure_count || 0);
if (isSuccess) {
if (newState === ‘half_open’) {
newState = ‘closed’; // Recovery confirmed
newFailures = 0;
}
newFailures = 0; // Reset on success
} else {
newFailures++;
if (newFailures >= 5 && newState === ‘closed’) {
newState = ‘open’; // Circuit tripped
}
}
return [{
json: {
cb_state: newState,
failure_count: newFailures,
opened_at: newState === ‘open’ ? new Date().toISOString() : null
}
}];
`
The circuit breaker protects your workflow from services that are consistently down. Instead of wasting retries on a dead endpoint, it blocks requests immediately until the service recovers.
Error Handling with IF Nodes
The IF node routes failed requests to error handlers. Combine it with retry logic for complete error management:
`
IF node condition: {{ $json.action == ‘retry’ }}
True branch: Wait node (dynamic delay) → Loop back to HTTP Request
False branch: Continue to next step
`
For the n8n workflow snapshot and version control approach, log every error to a dedicated table. This gives you a complete audit trail of failures and recoveries.
Best Practices for Fault Tolerance
Always set timeouts. Never leave them at the default infinite wait. A hanging request ties up workflow resources and blocks downstream processing.
Use exponential backoff. Linear delays waste time on failed requests. Exponential backoff gives services increasing recovery time between retries.
Implement circuit breakers for critical dependencies. The n8n enterprise edition features article covers enterprise-grade reliability features for teams that need higher availability.
Monitor failure rates. Track how often each external service fails. Consistent failures indicate a deeper problem that needs investigation, not just more retries.
Action Card: Fault Tolerance Template
Set up a resilient HTTP request pattern:
`bash
1. HTTP Request Node
– Timeout: 30000ms
– Retry on Fail: Yes
– Max Retries: 3
– Retry Wait: Exponential (start 1000ms)
2. Code Node: Parse response and check status
– If 2xx: proceed
– If 429: wait and retry
– If 5xx: retry with backoff
– If 4xx: route to error handler
3. IF Node: Route based on response
– Success: Continue workflow
– Retry: Wait node → Loop back
– Fatal: Error notification node
4. Slack/PagerDuty Node: Alert on persistent failures
`
This template handles most transient failure scenarios. Add circuit breaker logic for services that fail frequently.
