On this page 11 sections
n8n’s default on failure is to record the execution as failed and tell nobody. A production workflow needs three things the default does not give you: retry with backoff for transient errors, an error workflow that fires on permanent failure, and somewhere the failed record lands so it can be replayed. Alert on the dead-letter queue, not on individual failures.
The gap between a workflow that works and one that runs unattended is almost entirely this.
Key takeaways
- Retrying a permanent error is just failing more expensively. Classify first.
- Set the error workflow on every production workflow. It is one field and it is usually empty.
- A failed record needs somewhere to land, or the only recovery is finding it in the execution log by hand.
- Alert on the dead-letter queue depth, not on every failure - or people learn to ignore the channel.
- The workflow that succeeds while doing nothing is worse than one that fails loudly.
Who this applies to
You have n8n workflows running unattended - nightly syncs, webhook handlers, anything where a human is not watching the run. This assumes basic familiarity with building a workflow.
What happens by default
A node throws. The execution stops, gets marked failed, and appears in the executions list. That is the whole default behaviour.
For a workflow you trigger manually and watch, that is fine. For anything scheduled or webhook-driven, it means failure is silent until someone notices the downstream consequence - a report that did not arrive, a record that never synced, a customer who never got the email. That gap is typically days.
Classify the failure before you handle it
Three classes, and they need different responses. Treating them alike is the most common mistake.
Transient. A timeout, a 429 rate limit, a 503, a brief network fault. The same request will probably succeed shortly. Retry with backoff. Note what that does to the receiver: a retried delivery is a duplicate, and whatever is on the other end has to be built to expect one - see webhooks that drop.
Permanent. A 400 with a validation message, a 401, a 404 for a record that does not exist, a schema mismatch. Retrying is pointless and costs money - if you are paying per operation or per token, a permanent error retried three times costs four times as much to fail. Do not retry. Route it.
Poison. A record that fails every time and will keep failing - a malformed payload, a document the parser cannot read. Retrying blocks the queue behind it. Move it aside immediately so the rest of the batch proceeds.
n8n’s node-level retry does not distinguish between these. It retries whatever failed. So the classification has to happen in your logic: check the status code, and only retry the ones worth retrying. That logic is part of the workflow, which is a good reason for the workflow to live somewhere you can review a change to it - see version controlling n8n workflows.
The pattern
Retry, correctly
n8n gives you retry-on-fail at the node level with a configurable count and wait. Use it, with two constraints.
Only on nodes where retrying makes sense - HTTP calls to flaky services, rate-limited APIs. Not on a node that transforms data, because if that throws, it will throw again.
With backoff, not a fixed short wait. Retrying a rate-limited API three times in quick succession is how you stay rate-limited. Increasing waits give the far side room to recover.
Three attempts is usually the right number. Beyond that you are queueing failure rather than handling it.
The error workflow
Every n8n workflow has an error-workflow setting. It is empty by default, and setting it is the single highest-value change on this list.
When a workflow fails permanently, n8n triggers the workflow you nominate, passing the execution details. Build one error workflow and point every production workflow at it - you do not need one each.
What it should do:
- Write the failed record and the error to durable storage
- Increment whatever your alerting watches
- Include enough context to replay: the input data, the workflow name, the execution id, the timestamp
That third point is what makes recovery possible. An error workflow that posts “workflow failed” to a channel tells you something is wrong and gives you nothing to act on.
The dead-letter store
Somewhere failed records land so they can be inspected and replayed. A database table, a spreadsheet, a queue - the mechanism matters much less than its existence.
Minimum columns: the input payload, the error message, the workflow, the timestamp, and a status you can mark as resolved.
Then a second workflow that reads unresolved rows and re-runs them. Once you have that, a permanent failure becomes an item on a list rather than an archaeology exercise in the execution log.
Alert on the queue, not the event
The distinction that decides whether anyone reads your alerts.
Alerting on every failure means a flaky API produces forty messages overnight and the channel gets muted within a fortnight. Alert instead on dead-letter depth crossing a threshold, and on depth that has not returned to zero within a window.
That converts noise into a signal that means something: work is stuck and nobody has cleared it.
Replay needs idempotency
The dead-letter store is only useful if replaying from it is safe, and that is a property of the workflow rather than of the store.
If a workflow failed at step six of eight, replaying from the start re-runs steps one to five. Where those steps created a record, sent an email or charged something, replay duplicates it. Teams discover this the first time they clear a backlog and send four hundred people the same message twice.
Three ways to make replay safe, in order of preference:
Use the downstream system’s idempotency support. Many APIs accept an idempotency key - a value you generate per logical operation, which the far side uses to recognise a repeat and return the original result rather than acting again. Where this exists, use it; it is the only approach that is correct rather than merely careful.
Check before you write. Before creating a record, look for one with the same natural key. Slower, and it works with systems that offer nothing better. The gap between checking and writing is a race, which matters at concurrency and usually not at replay volumes.
Split the workflow at the side-effect boundary. Keep everything before the first irreversible action in one workflow, and the action itself in another. Replaying the first is then free by construction.
The general rule: anything that leaves your system is a side effect, and side effects are where replay hurts. Reading, transforming and validating can be repeated all day. Sending, charging and creating cannot, unless you have made them so.
Worth deciding this when you build the dead-letter store rather than when you first need to drain it, because the answer occasionally changes the workflow’s structure.
The failure nobody catches
Worth its own section because retries do not help: the workflow that succeeds while doing nothing.
An API returns 200 with an empty array because a filter parameter was wrong. A node maps a field that no longer exists and passes an empty string downstream. The execution is green. Zero records processed, and no error anywhere.
The only defence is asserting on the shape of what you got: if a sync that normally moves 200 records moves zero, that is a failure regardless of status codes. Add a node that checks the count and throws when it is implausible.
We wrote about the broader version of this in why automations fail silently.
What we set by default
Every production workflow we build gets the error workflow set, a dead-letter table, retry only on network-bound nodes, and a plausibility assertion on record counts. It is perhaps an hour of work per workflow and it is not optional in our builds.
The opinion behind it: a workflow without error handling is a demo that happens to be running in production. It will work for weeks, which is the problem - it accumulates trust it has not earned, and the first failure arrives after everyone has stopped watching.
The part we push back on hardest is alerting design. Clients frequently want a message on every failure, because it feels safer. It reliably produces a muted channel within a month, at which point the alerting is worse than none, because everyone believes it is working. We would rather ship a threshold alert somebody reads than a firehose everybody filters.
When this is over-engineering
A workflow you run manually and watch. You are the error handler.
Genuinely idempotent, frequently repeating syncs. If it runs every fifteen minutes and re-processes everything, a failed run self-heals on the next one. Alert on consecutive failures instead.
Prototypes. Do not build a dead-letter pipeline for something that may not survive the month.
Frequently asked questions
How many retries should we configure?
Three, with increasing waits, on network-bound nodes only. More than that is queueing failure rather than handling it.
Should the error workflow notify a person directly?
Not per failure. Have it write to the dead-letter store and let a threshold alert handle the human. Direct notification per error is the pattern that gets muted.
Where should the dead-letter store live?
Wherever you will actually look. A Postgres table is ideal; a spreadsheet is genuinely fine at low volume. The failure mode is not choosing a poor store, it is not having one.
What about workflows that fail because of bad input?
Those are poison records. Route them aside on the first failure rather than retrying, so the rest of the batch proceeds, and treat a rising count of them as a signal that something upstream changed.
Does this apply to Make as well?
The principles do - classify, retry only transient, land failures somewhere, alert on the queue. The mechanisms differ; Make has its own error-handler routes. See n8n vs Make.
Next step
If you have workflows in production with the error-workflow field empty, that is the cheapest reliability fix available to you. The n8n and Make engagement builds workflows with error handling and alerting as standard rather than as a phase two.
Related: Why automations fail silently · n8n vs Make · Self-hosted n8n · Business process automation