📁 last Posts

n8n Website Downtime Checker: Free Workflow Template with Zero False Alerts

n8n Website Downtime Checker: Free Workflow Template with Zero False Alerts

Whether you run a blog, an e‑commerce store, or a SaaS product, a website downtime checker is non‑negotiable. Every minute of downtime costs you visitors, sales, and trust. Most check site downtime tools limit you to 5-10 sites and check every 5 minutes at best on the free tier. This guide builds a free n8n website status test that catches outages within seconds — and unlike paid tools, it also monitors SSL expiry, logs historical data, and lets you run your own site uptime checker on unlimited domains without paying a cent.

With n8n, you can run a website uptime test every 60 seconds on unlimited domains, with custom logic for retries, SSL warnings, and historical reporting — all for the cost of your VPS. This same template is also available as a ready-to-import blueprint in our n8n Workflow Templates library.

The Direct Answer

An n8n website downtime checker is a free, self-hosted workflow that pings your site on a schedule, detects downtime within seconds, and instantly alerts you on Telegram, Slack, or email — with zero monthly subscription.

Unlike UptimeRobot or Pingdom, you own the data, monitor unlimited sites, and control the exact alert logic, including SSL expiry warnings and historical uptime logging. The full copy-paste-ready build is below.

Why Build an Uptime Checker in n8n

I run TriggerWorkflow.com on Blogger and I've lost traffic before to silent outages I only discovered a day later through Search Console impressions dropping. Paid uptime tools are fine, but most limit you to 5-10 free monitors, check every 5 minutes at best on the free tier, and don't let you customize the alert logic. With n8n you get 1-minute checks, unlimited monitors, and full control over what counts as "down."

This guide builds a production-grade checker, not a toy demo — with retry logic so a dropped packet doesn't wake you at 3 AM, SSL expiry warnings, and a historical log you can chart later.

💡
New to n8n? Start with our 30-day n8n learning roadmap before this build — it covers triggers, HTTP requests, and error handling used here.

Workflow Architecture

BlockRole
Schedule TriggerRuns every 1-5 minutes
HTTP RequestPings your URL with a strict timeout, continues on error
IF nodeEvaluates status code and response time against thresholds
Retry sub-flowA second check before declaring real downtime
Alert + LogSends the notification and writes a row to Google Sheets

Step-by-Step Build Guide

1

Schedule Trigger

Add a Schedule Trigger node. Every 2 minutes for a personal site, every 1 minute if uptime is business-critical. Avoid going below 1 minute on n8n Cloud's free tier — you'll hit execution limits fast.

2

HTTP Request Node

Method GET, timeout 10000ms, Response set to Full Response so you can read statusCode, On Error set to "Continue (using error output)," and follow redirects enabled.

3

IF Node: Detect Downtime

Connect both HTTP Request outputs into the IF node. Use OR logic: {{$json.statusCode}} not between 200-299, OR {{$json.error}} exists.

4

Set Node: Normalize the Payload

On the "down" branch, add a Set node with the current assignments structure so downstream nodes always get the same shape, regardless of whether the failure was a timeout or a bad status code.

⚠️
If "On Error" stays on "Stop Workflow," a single timeout kills the whole execution and you never get an alert. Always use "Continue (using error output)."
{
  "parameters": {
    "mode": "manual",
    "assignments": {
      "assignments": [
        { "id": "a1", "name": "site", "value": "={{$('Schedule Trigger').item.json.site || 'https://triggerworkflow.com'}}", "type": "string" },
        { "id": "a2", "name": "status_code", "value": "={{$json.statusCode || 0}}", "type": "number" },
        { "id": "a3", "name": "checked_at", "value": "={{$now.toISO()}}", "type": "string" },
        { "id": "a4", "name": "reason", "value": "={{$json.error ? $json.error.message : 'Bad status code'}}", "type": "string" }
      ]
    },
    "options": {}
  },
  "type": "n8n-nodes-base.set",
  "typeVersion": 3,
  "name": "Normalize Downtime Data"
}

Fixing False Positives

The most common complaint about DIY uptime checkers is alert fatigue from transient network blips. Fix it with a wait-and-retry pattern instead of alerting on the first failure:

  1. On the "down" branch, add a Wait node set to 30 seconds.
  2. Add a second HTTP Request node hitting the same URL again.
  3. Add a second IF node checking the same failure condition.
  4. Only if the second check also fails does the workflow continue to the alert branch.
✅
This two-step confirmation pattern eliminates the vast majority of false alerts caused by momentary DNS hiccups or CDN edge issues, based on months of running this exact setup on TriggerWorkflow.com.

Monitoring Multiple Sites in One Workflow

To check more than one URL, replace the single HTTP Request node with a loop over a list:

  1. Store your URLs in a Google Sheet (column A: url, column B: name).
  2. Add a Google Sheets (Read) node before the HTTP Request.
  3. Use a Split In Batches node or Loop Over Items to check each site.
  4. Add a Merge node to combine results before logging.
💡
You can also use an Airtable or Supabase table instead of Google Sheets — the logic stays identical.
ℹ️
This is the first three nodes only — showing how to read your URL list and start the loop. Wire its output into the same Is Down? → Wait → Recheck → Alert/Log logic from the "Complete Importable Workflow JSON" section below, replacing that section's single hardcoded URL.
{
  "name": "Batch Check Multiple Sites (partial)",
  "nodes": [
    { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 5 } ] } }, "id": "trigger1", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [200,300] },
    { "parameters": { "operation": "read", "documentId": { "__rl": true, "value": "YOUR_SHEET_ID", "mode": "id" }, "sheetName": { "__rl": true, "value": "Sheet1", "mode": "name" } }, "id": "sheets1", "name": "Read Site List", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [420,300] },
    { "parameters": { "batchSize": 5, "options": {} }, "id": "batch1", "name": "Split In Batches", "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, "position": [640,300] },
    { "parameters": { "url": "={{ $json.url }}", "options": { "timeout": 10000, "response": { "response": { "fullResponse": true } } } }, "id": "http1", "name": "Check Website", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "onError": "continueErrorOutput", "position": [860,300] }
  ],
  "connections": {
    "Schedule Trigger": { "main": [ [ { "node": "Read Site List", "type": "main", "index": 0 } ] ] },
    "Read Site List": { "main": [ [ { "node": "Split In Batches", "type": "main", "index": 0 } ] ] },
    "Split In Batches": { "main": [ [], [ { "node": "Check Website", "type": "main", "index": 0 } ] ] }
  }
}

The Google Sheet needs two columns: url and name. Replace YOUR_SHEET_ID with your sheet's ID from its URL, and connect Check Website's output into the Is Down? node from the full JSON further down this page — then loop back into Split In Batches' second output to process the next batch.


Bonus: SSL Certificate Expiry Monitoring

Expired SSL certificates cause outages that a simple status-code check won't catch until it's too late. Add a parallel branch off the Schedule Trigger:

HTTP Request Node:
  Method: GET
  URL: https://api.ssllabs.com/api/v3/analyze?host=triggerworkflow.com&fromCache=on

Code node to extract the expiry date:
const endpoints = $input.first().json.endpoints || [];
const daysLeft = endpoints.length
  ? Math.floor((new Date($input.first().json.certExpiry) - Date.now()) / 86400000)
  : null;
return [{ json: { daysLeft } }];

Follow it with an IF node checking {{$json.daysLeft}} < 14 to trigger an early-warning alert on a separate channel from your downtime alerts.

ℹ️
Self-hosted alternative: if your n8n instance has the Execute Command node enabled, run openssl s_client -connect triggerworkflow.com:443 -servername triggerworkflow.com | openssl x509 -noout -enddate for a faster, rate-limit-free check. Execute Command is disabled by default on n8n Cloud and on many self-hosted setups for security reasons — check your instance's node settings first.

Logging Historical Data & Uptime Percentage

Add a Google Sheets node (Append Row) at the end of both the healthy and down branches, logging: timestamp, site, status_code, response_time_ms, result. Over a few weeks this gives you a real uptime percentage instead of guessing.

Calculating Uptime Percentage

With historical data in Google Sheets, calculate your uptime percentage using this formula:

Uptime % = (Total Checks - Down Checks) / Total Checks × 100

You can create a separate n8n workflow that runs weekly and writes this percentage to a dashboard Sheet or sends a report email. This is exactly the reporting pattern used in our Zero-Budget Lead Pipeline guide — same Google Sheets logging approach, adapted here for uptime instead of leads.


Pausing and Resuming the Workflow During Maintenance

To stop alerts during planned maintenance, add a webhook that sets a $workflow.staticData.paused flag:

// Webhook node → Code node
$workflow.staticData.paused = $input.first().json.paused || false;
return [];

Then add an IF node right after the Schedule Trigger that stops the workflow if the flag is true.

💡
Call https://your-n8n.com/webhook/your-webhook-id?paused=true to pause, and ?paused=false to resume. This approach is also useful for temporarily disabling monitors during deployments.

Adding an AI Incident Summary Agent

For sites with multiple monitored URLs, raw alerts get noisy fast. Route downtime events into an AI Agent node connected to an n8n MCP server via an ai_tool connection (never a main connection) that reads the last 10 incidents from your Sheet and writes a plain-English daily digest.


Complete Importable Workflow JSON

Copy the block below and import via n8n's "Import from File," or paste onto a blank canvas with Ctrl+V.

{
  "name": "Website Uptime Checker",
  "nodes": [
    { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 2 } ] } }, "id": "trigger1", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [200,300] },
    { "parameters": { "url": "https://triggerworkflow.com", "options": { "timeout": 10000, "response": { "response": { "fullResponse": true } }, "redirect": { "redirect": { "followRedirects": true } } } }, "id": "http1", "name": "Check Website", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "onError": "continueErrorOutput", "position": [420,300] },
    { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" }, "combinator": "or", "conditions": [ { "leftValue": "={{$json.statusCode}}", "rightValue": 299, "operator": { "type": "number", "operation": "gt" } }, { "leftValue": "={{$json.error}}", "rightValue": "", "operator": { "type": "string", "operation": "exists" } } ] } }, "id": "if1", "name": "Is Down?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [640,300] },
    { "parameters": { "amount": 30, "unit": "seconds" }, "id": "wait1", "name": "Wait 30s", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [860,220] },
    { "parameters": { "url": "https://triggerworkflow.com", "options": { "timeout": 10000, "response": { "response": { "fullResponse": true } } } }, "id": "http2", "name": "Recheck Website", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "onError": "continueErrorOutput", "position": [1060,220] },
    { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" }, "combinator": "or", "conditions": [ { "leftValue": "={{$json.statusCode}}", "rightValue": 299, "operator": { "type": "number", "operation": "gt" } }, { "leftValue": "={{$json.error}}", "rightValue": "", "operator": { "type": "string", "operation": "exists" } } ] } }, "id": "if2", "name": "Confirmed Down?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [1260,220] },
    { "parameters": { "mode": "manual", "assignments": { "assignments": [ { "id": "a1", "name": "site", "value": "https://triggerworkflow.com", "type": "string" }, { "id": "a2", "name": "status_code", "value": "={{$json.statusCode || 0}}", "type": "number" }, { "id": "a3", "name": "checked_at", "value": "={{$now.toISO()}}", "type": "string" }, { "id": "a4", "name": "reason", "value": "={{$json.error ? $json.error.message : 'Bad status code'}}", "type": "string" } ] } }, "id": "set1", "name": "Normalize Downtime Data", "type": "n8n-nodes-base.set", "typeVersion": 3, "position": [1460,220] },
    { "parameters": { "chatId": "YOUR_TELEGRAM_CHAT_ID", "text": "=🔴 DOWN: {{$json.site}} — {{$json.reason}} at {{$json.checked_at}}" }, "id": "telegram1", "name": "Send Telegram Alert", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [1660,220] }
  ],
  "connections": {
    "Schedule Trigger": { "main": [ [ { "node": "Check Website", "type": "main", "index": 0 } ] ] },
    "Check Website": { "main": [ [ { "node": "Is Down?", "type": "main", "index": 0 } ], [ { "node": "Is Down?", "type": "main", "index": 0 } ] ] },
    "Is Down?": { "main": [ [ { "node": "Wait 30s", "type": "main", "index": 0 } ], [] ] },
    "Wait 30s": { "main": [ [ { "node": "Recheck Website", "type": "main", "index": 0 } ] ] },
    "Recheck Website": { "main": [ [ { "node": "Confirmed Down?", "type": "main", "index": 0 } ], [ { "node": "Confirmed Down?", "type": "main", "index": 0 } ] ] },
    "Confirmed Down?": { "main": [ [ { "node": "Normalize Downtime Data", "type": "main", "index": 0 } ], [] ] },
    "Normalize Downtime Data": { "main": [ [ { "node": "Send Telegram Alert", "type": "main", "index": 0 } ] ] }
  }
}
💡
Replace YOUR_TELEGRAM_CHAT_ID with your own chat ID, and swap the Telegram node for Slack or Email if you prefer — the rest of the workflow stays identical.

Common Errors and Fixes

1. "ETIMEDOUT" on every execution even when the site is up

Your timeout is too low for a slow server, or your instance's outbound network is throttled. Raise the timeout to 15000ms and confirm your host allows outbound HTTPS on port 443.

2. Workflow stops entirely instead of alerting

The HTTP Request node's "On Error" is still "Stop Workflow." Change it to "Continue (using error output)" so the IF node receives the failed execution.

3. IF node always evaluates to "false" even when the site is down

Field type validation is "strict" but statusCode arrives as a string. Set typeValidation to "loose" in the IF node's options.

4. Telegram alert sends but the message is empty

You referenced {{$json.site}} after a node that no longer has that field. Use {{$('Normalize Downtime Data').item.json.site}} instead.

5. Google Sheets logging fails with "authentication error"

Your OAuth2 credential expired or was revoked. Reconnect it in n8n's Credentials tab and re-select it in the node.

6. Schedule Trigger runs but nothing happens on n8n Cloud

Free-tier n8n Cloud workflows pause after inactivity or hit execution caps. Check your usage dashboard and increase the interval if you're near the limit.

If you're hitting errors specific to the HTTP Request node itself, our Check URL Node guide covers the most common timeout and success-handling failures in depth.


Frequently Asked Questions

Is an n8n uptime checker really free?

Yes, if you self-host n8n on a free-tier VPS or use n8n Cloud's free plan within its execution limits. You only pay for hosting, which can be $0 on services like Oracle Cloud's free tier.

How often should I check my website's uptime?

Every 1-2 minutes for business-critical sites, every 5 minutes for personal blogs. Going below 1 minute rarely adds value and burns through execution quotas faster.

Can I monitor multiple websites in one workflow?

Yes. Replace the single URL with a Sheet or Airtable list of sites, then use Split In Batches before the HTTP Request to loop through each one.

Does this replace UptimeRobot or Pingdom?

For most personal and small business sites, yes. You lose their public status-page feature, but gain unlimited monitors and full alert-logic control.

Why use a retry step instead of alerting immediately?

A single failed request is often a transient network blip, not real downtime. The 30-second wait-and-recheck pattern cuts false alerts without meaningfully delaying real detection.

Can I get an SMS alert instead of Telegram or Slack?

Yes, swap the Telegram node for a Twilio node using the same input data — nothing else needs to change.

Does n8n need to run 24/7 for this to work?

Yes, the Schedule Trigger only fires while n8n is running, so it must be hosted somewhere persistent, not on a local laptop.


Explore More Guides

Ahmed Ayari — TriggerWorkflow I built and tested this exact workflow on TriggerWorkflow.com before writing this guide, including the retry logic that came from real false-alert pain. The code in this guide is the same stack I use to monitor my own sites — if it breaks for you, it would have broken for me first. Read more guides by Mr.Ayari
Comments