Tutorials n8n

n8n Error Handling: Error Workflows, Alerts, and Retries

Set up error handling in n8n 2.x: Retry On Fail and On Error on nodes, an Error Trigger workflow that sends a Telegram or email alert with a link to the failed execution, Stop and Error for silent failures, retrying executions, and handling 429 rate limits.

The RoamerHost Team 22 min read
Ready to start? Managed n8n hosting, set up in 60 seconds Workflow automation platform. From avg. NT$499/mo (billed yearly). Subscribe to n8n
On this page

This guide walks you through a complete error-handling setup in n8n: automatic retries on nodes that tend to fail, a decision on whether a bad item should stop the run or be routed aside, and a dedicated error workflow that sends you a Telegram or email alert with a link to the failed execution, so you can open it and retry from the node that broke. You need an n8n 2.x instance (self-hosted or managed), one channel for alerts (a Telegram bot, Gmail, or Slack), and an existing workflow to test with. Plan on about half an hour.

Based on the official n8n docs and the n8n 2.45 source code on GitHub. UI labels follow the 2.x English interface. Information verified as of October 2026.

Two layers: a node error vs. a failed workflow

Error handling in n8n happens at two levels, and mixing them up is the most common reason people never get an alert. The first level is the node. Every node has a Settings tab with Retry On Fail and On Error, which decide whether the node tries again and what happens if it still fails. The second level is the workflow. When an execution ends with a failed status, n8n starts the error workflow you assigned, and that workflow sends the alert or cleans up.

The link between the two matters: the second level only runs when the execution actually fails. If you set a node's On Error to Continue, the error is swallowed, the execution ends as successful, and your error workflow never hears about it. So before you touch any settings, decide which errors a node can absorb on its own (an occasional timeout, one malformed record) and which ones a person must know about (an expired credential, a service outage, a batch that came back empty). Let the second group fail the workflow and hand it to the error workflow.

SettingWhereGood for
Retry On FailNode Settings tabOccasional timeouts, 502/503 responses, per-second rate limits
On ErrorNode Settings tabStopping, continuing, or routing failed items to a separate branch
Error WorkflowWorkflow menu → SettingsAlerting, logging, and cleanup when the whole run fails
Stop and ErrorA node on the canvasFailing on purpose when nothing errored but the result is wrong

Node level, part 1: Retry On Fail

Open any node and switch from Parameters to the Settings tab. Turn on Retry On Fail and two fields appear. Max. Tries is the total number of attempts, including the first one; it defaults to 3 and accepts 2 to 5. Wait Between Tries (ms) is the pause between attempts in milliseconds; it defaults to 1000 and tops out at 5000. Even at the maximum, that is five attempts five seconds apart, roughly 20 seconds of waiting in total. The limits are enforced in the execution engine, so typing a larger number does not help.

That window is right for errors that clear up on their own: an upstream server returning 502 or 503 now and then, a connection timeout, or a per-second rate limit. It is pointless for errors that will not change, such as a 401 from a bad credential, a 400 from malformed parameters, or a 404 for a record that does not exist. Retrying those just delays the failure by 20 seconds. My rule of thumb is to enable Retry On Fail on nodes that call external services, like HTTP Request, AI model nodes, or Google Sheets, and leave it off for internal nodes like IF, Edit Fields, and Code.

One caveat: a retry re-runs the whole node. If an HTTP Request node is processing 100 items and item 87 fails, the retry sends the first 86 again. For a read, that is harmless. For sending email, creating orders, or writing to a database, it can mean duplicate emails or duplicate records. Before enabling retries on a write, make sure the target API deduplicates (for example, by accepting a unique order ID), or process one item at a time.

Node level, part 2: the three On Error options

Also on the Settings tab, On Error decides what happens once retries are exhausted or the node fails. There are three options.

Stop Workflow is the default. The node fails, the execution stops immediately, the run is marked as failed, and your error workflow is triggered. Most nodes should stay on the default, especially the ones early in a workflow that fetch data. If the data never arrived, nothing downstream makes sense.

Continue wraps the error message in an item and passes it down the regular output, where the next node can read it as {{ $json.error }}. The execution runs to the end and is marked successful. That fits cases where losing one item is acceptable, but downstream nodes now receive an item that lacks the fields they expect, and they tend to fail later in confusing ways.

Continue (using error output) is the one I use most. It adds a second output, labeled error, to the node. Successful items continue along the normal output, and failed items take the error branch. Items on the error branch keep their original fields plus an error field with the details, so you can write them to a "to review" sheet, send an alert, or wait and resend them (the 429 section below uses this).

Keep in mind that once the error branch finishes, the execution still ends as successful. If you want to handle failed items and still have the run count as failed so the error workflow fires, put a Stop and Error node at the end of the error branch. More on that node below.

Workflow level: building an error workflow with Error Trigger

An error workflow is an ordinary workflow that starts with an Error Trigger node. When another workflow fails, n8n passes the failure details to it, and you can attach any nodes you like: send an alert, log to a spreadsheet, open a ticket. One error workflow can serve many workflows, and a single one is usually enough for an entire n8n instance. If you have never built a workflow before, start with building your first workflow to get comfortable with the editor.

Step 1: Create and publish the error workflow

  1. On the n8n home page, select Create workflow and give it an obvious name such as "Error alerts".
  2. For the first node, search for Error Trigger. It has no parameters, only a note saying it fires when another workflow that is set up to use it fails.
  3. Attach your alert node (the next section uses Telegram) and save.
  4. Select Publish in the upper-right corner. In n8n 2.x you cannot skip this step; see below.

Many tutorials, and the official Error Trigger page, say an error workflow does not need to be published (or "activated" in older versions). That was true in 1.x. Since 2.x split workflows into drafts and published versions, n8n runs the published version of the error workflow. If it was never published, failing workflows cannot start it, the server log shows ... is not active and cannot be executed, and you get no alert at all. Newer releases catch this earlier: when you assign the error workflow, the save is rejected with The selected error workflow has no published version. For the same reason, any later change to the error workflow only takes effect after you publish again. Saving alone leaves the old published version running.

Step 2: Assign the error workflow

  1. Open the workflow you want to monitor, select the three-dot Workflow menu in the upper-right corner, and choose Settings.
  2. Under Error Workflow (to notify when this one errors), pick the "Error alerts" workflow.
  3. Select Save. According to the docs, when you change only settings on a published workflow, n8n republishes it for you.

The setting is stored per workflow, so you have to repeat it for every workflow you want covered. It is easy to miss one, so make it a habit to set the error workflow whenever you create a new workflow.

If the error workflow lives in a different project from the workflows it monitors, also check the This workflow can be called by setting on the error workflow. n8n checks it before running the error workflow, and by default only workflows from the same owner may call it. If the check fails, the error workflow is blocked and the only trace is a permission error in its own execution list. The simplest fix is to keep the error workflow in the same project, or widen that setting.

What the error data contains

Here is roughly what the Error Trigger receives. The execution URL format below follows the 2.x source code, which is /workflow/WORKFLOW_ID/executions/EXECUTION_ID; the sample in the official docs still shows an older format:

[
  {
    "execution": {
      "id": "231",
      "url": "https://n8n.example.com/workflow/1/executions/231",
      "retryOf": "34",
      "error": {
        "message": "Example Error Message",
        "stack": "Stacktrace"
      },
      "lastNodeExecuted": "Node With Error",
      "mode": "trigger"
    },
    "workflow": {
      "id": "1",
      "name": "Example Workflow"
    }
  }
]

The fields you will use most are {{ $json.workflow.name }} for the name of the failed workflow, {{ $json.execution.lastNodeExecuted }} for the last node that ran (usually the one that failed), {{ $json.execution.error.message }} for the error message, {{ $json.execution.url }} for a link that opens the execution with every node's input and output and the failing node highlighted in red, and {{ $json.execution.id }} for the execution ID. execution.mode tells you how the run started, for example trigger for a schedule, webhook for a webhook, or retry for a manual retry from the execution list. retryOf only appears when the failed run was itself a retry, and holds the ID of the original execution.

One exception: if the failure happens in the trigger node itself, for example a Gmail Trigger whose credential has expired so the workflow never starts, there is no execution object. You get a trigger object instead, the message is at {{ $json.trigger.error.message }}, and there is no execution URL. Your alert expression needs to handle this case, or the error workflow will itself fail when it tries to read $json.execution.url.

Hands-on: a Telegram alert with a link to the failed execution

I use Telegram here because a bot takes five minutes to set up and needs no approval. If you want alerts in LINE, note that LINE Notify shut down on March 31, 2025; you would need the LINE Messaging API instead, which takes more setup and is out of scope for this guide.

  1. In Telegram, search for BotFather, send /newbot, follow the prompts, and copy the bot token it gives you.
  2. Send any message to your new bot, then open https://api.telegram.org/botYOUR_TOKEN/getUpdates in a browser. The id under chat in the response is your Chat ID. For a group, add the bot to the group, post a message there, and check again; group Chat IDs are negative numbers.
  3. Back in the "Error alerts" workflow, add a Telegram node after the Error Trigger and choose the Send a text message action. Under Credential, select Create new credential and paste the bot token into Access Token.
  4. Enter your Chat ID, switch the Text field to Expression, and paste the content below.
  5. Under Additional Fields, add Parse Mode and set it to HTML, then add Append n8n Attribution and turn it off so the message does not end with an n8n signature line.
[n8n workflow failed]
Workflow: {{ $json.workflow.name }}
Failed node: {{ $json.execution ? $json.execution.lastNodeExecuted : 'trigger node' }}
Error: {{ (($json.execution || $json.trigger).error.message || '').slice(0, 500).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;') }}
Time: {{ $now.toFormat('yyyy-MM-dd HH:mm') }}
Execution: {{ $json.execution ? $json.execution.url : 'trigger failed, no execution saved' }}

The error line does three things. It falls back to the trigger object when the trigger node failed. It keeps only the first 500 characters, because some APIs return a full HTML page on error and Telegram caps a message at 4,096 characters. And it escapes &, <, and >, because with Parse Mode set to HTML a single stray angle bracket makes Telegram reply with Bad Request: can't parse entities and the whole alert is lost.

Why not leave Parse Mode unset? According to the n8n source, the Telegram node falls back to legacy Markdown when no parse mode is set, so an underscore or asterisk in an error message or URL triggers the same can't parse entities error. HTML plus your own escaping is the most reliable combination I have found. The time line uses the workflow's time zone. RoamerHost's managed n8n is preset to Taiwan time; if you self-host, set GENERIC_TIMEZONE to your zone, because n8n otherwise defaults to New York time.

To skip the manual setup, copy the JSON below, click the n8n canvas, and press Ctrl+V (Cmd+V on a Mac). Both nodes are pasted already connected; then attach your Telegram credential and replace the Chat ID:

{
  "nodes": [
    {
      "parameters": {},
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [0, 0]
    },
    {
      "parameters": {
        "chatId": "123456789",
        "text": "=[n8n workflow failed]\nWorkflow: {{ $json.workflow.name }}\nFailed node: {{ $json.execution ? $json.execution.lastNodeExecuted : 'trigger node' }}\nError: {{ (($json.execution || $json.trigger).error.message || '').slice(0, 500).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;') }}\nTime: {{ $now.toFormat('yyyy-MM-dd HH:mm') }}\nExecution: {{ $json.execution ? $json.execution.url : 'trigger failed, no execution saved' }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "name": "Telegram alert",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [240, 0]
    }
  ],
  "connections": {
    "Error Trigger": {
      "main": [[{ "node": "Telegram alert", "type": "main", "index": 0 }]]
    }
  }
}

Step 3: Test it (manual runs do not trigger the error workflow)

This is where most people get stuck. If you select Execute workflow in the editor and the run fails, the error workflow does not fire. The docs state that the Error Trigger only runs when an automatic execution fails, and the source code simply skips executions in manual mode. So test in two stages.

First, test the message format. Open the "Error alerts" workflow and select Execute workflow. In a manual run, the Error Trigger outputs sample data (error message Example Error Message, execution ID 231), and the Telegram node sends a test alert so you can check the layout and expressions.

Second, test the full path. Create an "Error test" workflow with a Schedule Trigger (Trigger Interval set to Minutes, Minutes Between Triggers set to 1) followed by a Stop and Error node with the message "This is a test error". In its Settings, set Error Workflow to "Error alerts", then publish it. Within a minute or two you should get a Telegram alert, and the link should open the failed execution. When you are done, unpublish the test workflow, or it will fail and alert you every minute.

To use email or Slack instead, swap the Telegram node for a Gmail node (Resource: Message, Operation: Send) or a Slack node (also Message, then Send) and reuse the same expression; you can drop the HTML escaping there. A subject such as [n8n failed] {{ $json.workflow.name }} makes the workflow obvious in your inbox. For critical workflows, use both channels: set the Telegram node's On Error to Continue (using error output) and connect the error branch to Gmail, so you still get an email if Telegram is unreachable.

Stop and Error: failing on purpose when the result is wrong

The hardest failures to notice are the ones that look like success. Say you pull yesterday's orders every morning for a report, and the API returns 200 with an empty array. Or it returns something like {"ok": false, "error": "token expired"}, an error wrapped in a normal response. n8n judges an HTTP Request by its status code, so both cases count as success and your error workflow never hears about them.

The fix is an IF node after the fetch step that checks the result, for example {{ $json.ok }} is false, or {{ $json.orders.length }} equals 0, with the true branch going to a Stop and Error node. Stop and Error has two error types. Error Message takes text and supports expressions, such as API call failed: {{ $json.error }}. Error Object takes a JSON object, which is useful when you want to pass an error code along. When the run reaches this node it ends as failed, the error workflow receives your text as execution.error.message, and lastNodeExecuted is the name of the Stop and Error node. That is a good reason to give it a descriptive name such as "Zero orders today", so the alert explains itself.

Retrying a failed execution

Once you have the alert and have fixed the cause, you do not need to trigger the workflow again. By default n8n saves failed production executions, and you can resume from the node that failed.

  1. Open the execution link from the alert, or go to Overview, open the Executions tab, select Filters, and set Status to Failed.
  2. Open the three-dot menu on that row and choose Retry with currently saved workflow (from node with error) or Retry with original workflow (from node with error). In a workflow's own Executions tab, the same two options are labeled Retry with current version and Retry with original version.
  3. The retry creates a new execution marked Retry of the original ID. Once a retry succeeds, the original execution can no longer be retried.

The difference is which version of the workflow runs. Currently saved (current version) uses what you have saved now, which is what you want after editing a node. Original uses the version that ran at the time, which fits when the workflow was fine and the external service was briefly down. Both resume at the failed node, reuse the saved output of the nodes that already succeeded, and do not fire the trigger or re-fetch data. If the retry fails again, the error workflow runs again, this time with retryOf in the data.

A few limits apply. The failed node reprocesses all of its input items, including the ones it handled successfully before, so for email or write operations check the target system first. If you renamed or deleted the failed node and then retry with the currently saved version, you get Could not find the node "..." in workflow. It probably got deleted or renamed. In that case retry with the original version or trigger the workflow again. Finally, executions that were never saved cannot be retried. If you set Save failed production executions to Do not save to save disk space, there is nothing to retry and the link in your alert leads nowhere. For how much history to keep and how to prune it, see execution data growth and cleanup.

Handling 429 rate limits from external APIs

429 Too Many Requests is one of the most common errors when you integrate external services. n8n usually shows it as The service is receiving too many requests from you, and the HTTP Request node may also include a hint suggesting the Batching option under Options. The right fix depends on how the service limits you, so check its API docs to see whether the limit is per second, per minute, or per day.

For a per-second limit, Retry On Fail is enough. Set Max. Tries to 3 to 5 and make Wait Between Tries longer than the limit window. If the API allows one request per second, use 1000 or more.

Better still, slow down before you hit the limit. In the HTTP Request node, add the Batching option under Options. Items per Batch sets how many items go out per batch (default 50), and Batch Interval (ms) sets the pause between batches (default 1000). For an API that allows 60 requests per minute, use 1 item per batch and a 1000 ms interval. For nodes without a Batching option, such as AI model or Google Sheets nodes, place a Loop Over Items node before them and a Wait node after them that loops back to Loop Over Items.

For per-minute or longer limits, the roughly 20 seconds Retry On Fail can wait is not enough. Build a loop that waits longer and resends:

HTTP Request
  Settings → Retry On Fail: on, Max. Tries 3, Wait Between Tries (ms) 5000
  Settings → On Error: Continue (using error output)
    error output → IF ({{ $runIndex }} is less than 3)
      true  → Wait (Resume: After Time Interval, 60 Seconds) → back into HTTP Request
      false → Stop and Error ("Still rate limited after 3 rounds")

$runIndex is how many times the current node has run in this execution, starting at 0. It goes up by one each time the IF node is reached, which caps the loop at three rounds instead of spinning forever while the API keeps refusing you. Items on the error branch keep their original fields, so when they flow back into HTTP Request, any parameters built from $json still resolve. After three failed rounds, Stop and Error fails the run and your error workflow alerts you.

The last case is a daily or monthly quota, such as an AI provider's daily token cap. No amount of retrying helps. Let the run fail, take the alert, and once the quota resets, use Retry with original workflow from the execution list.

Common pitfalls

A manual run failed and no alert arrived

That is expected. Error workflows only handle failures in automatic executions, and runs started with Execute workflow in the editor never trigger them. Use a published scheduled workflow, as in the "Error test" setup above.

A production run failed and the error workflow still did nothing

Check in this order: is the error workflow published, and republished after your last edit? Does it start with an Error Trigger that is not deactivated? Is it selected under Error Workflow in the failed workflow's Settings? Are the two workflows in different projects and blocked by This workflow can be called by? Also confirm the failing node is not set to Continue, because a swallowed error never fails the run.

The link in the alert points to http://localhost:5678

n8n builds the execution URL from its public URL settings. It checks N8N_EDITOR_BASE_URL first, then WEBHOOK_URL, and falls back to http://localhost:5678. If you self-host with Docker behind a reverse proxy, set both to your https address and recreate the container. Broken webhook URLs have the same cause; see the webhook and third-party integration guide.

Telegram replies with can't parse entities

The message contains characters Telegram cannot parse. Set Parse Mode to HTML as in the example above and escape &, <, and > in the error message. If workflow names might contain angle brackets, escape those too.

The error workflow fails itself

To prevent infinite loops, n8n does not run an error workflow to handle its own failures, which means a broken alert node fails silently. Keep the error workflow as simple as possible, enable Retry On Fail on the alert node, update its Telegram or Gmail credential whenever you rotate it, and glance at its Executions tab now and then for red entries.

If you would rather not run the server yourself

With alerts in place, what usually takes a workflow down at night is the server: a container that crashed with nobody to restart it, a disk filled with execution data, or a version that fell behind on security patches. If you would rather spend your time on workflows, RoamerHost's fully managed n8n runs in an isolated container with its own HTTPS subdomain and webhook URLs, so the execution links in your alerts open directly. It comes preset to Taiwan time, includes automatic updates and automatic backups, and has no caps on executions or workflows, for NT$5,988 per year (avg. NT$499/mo, billed yearly), with support in Chinese. To compare against self-hosting, see the self-hosting cost breakdown.

FAQ

Q: Does the error workflow have to be published?

In n8n 2.x, yes. n8n runs the published version, so an unpublished error workflow never runs, and newer versions reject it when you try to assign it. The "no need to publish" note in the Error Trigger docs describes 1.x behavior. Remember to republish after every change.

Q: Can one error workflow cover all my workflows?

Yes. A single Error Trigger workflow can receive failures from every workflow, and $json.workflow.name tells you which one broke. You still need to select it in each workflow's Settings. To route alerts to different people, add a Switch node after the Error Trigger and branch on the workflow name.

Q: Can Retry On Fail wait a full minute between attempts?

No. Wait Between Tries maxes out at 5000 ms and Max. Tries at 5. For longer waits, set On Error to Continue (using error output) and loop through a Wait node back into the original node, as shown in the 429 section.

Q: Does retrying a failed execution start from the beginning?

No. A retry starts at the failed node and reuses the saved output of earlier nodes; the trigger does not fire again. The failed node does reprocess all of its input items, so check the target system before retrying a write operation.

Sources

Ready to get started with n8n?

60 seconds after you subscribe, n8n is installed for you — an isolated container with hard resource limits you never share, and HTTPS out of the box.

Billed yearly · no cancellation fee · cancel renewal anytime

Hi, I'm Roamer! Tap me anytime with a question and I'll help you out.

Roamer

Roamer - AI assistant

Online
Roamer

Ask me anything, anytime — I'll do my best to help!

Powered by RoamerHost AI