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.
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.
On this page
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.
Workflow Architecture
| Block | Role |
|---|---|
| Schedule Trigger | Runs every 1-5 minutes |
| HTTP Request | Pings your URL with a strict timeout, continues on error |
| IF node | Evaluates status code and response time against thresholds |
| Retry sub-flow | A second check before declaring real downtime |
| Alert + Log | Sends the notification and writes a row to Google Sheets |
Step-by-Step Build Guide
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.
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.
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.
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.
{
"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:
- On the "down" branch, add a Wait node set to 30 seconds.
- Add a second HTTP Request node hitting the same URL again.
- Add a second IF node checking the same failure condition.
- Only if the second check also fails does the workflow continue to the alert branch.
Monitoring Multiple Sites in One Workflow
To check more than one URL, replace the single HTTP Request node with a loop over a list:
- Store your URLs in a Google Sheet (column A:
url, column B:name). - Add a Google Sheets (Read) node before the HTTP Request.
- Use a Split In Batches node or Loop Over Items to check each site.
- Add a Merge node to combine results before logging.
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.
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.
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 } ] ] }
}
}
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
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.
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.
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.
For most personal and small business sites, yes. You lose their public status-page feature, but gain unlimited monitors and full alert-logic control.
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.
Yes, swap the Telegram node for a Twilio node using the same input data — nothing else needs to change.
Yes, the Schedule Trigger only fires while n8n is running, so it must be hosted somewhere persistent, not on a local laptop.
