n8n: check a site every hour and log whether it is up
A workflow that requests a site once an hour and appends one line per check to a log file — OK when the site answers 200, FAIL with the real status code when it does not. The point is the log: a check that only records the good hours is not a log. The workflow file below is the exact file that was imported and run for this guide.
The workflow at a glance
Node versions in the file: Schedule Trigger 1.4, HTTP Request 4.5, IF 2.4, Code 2, Convert to File 1.2, Read/Write Files 1.1.
Key result: every hour, the workflow requests https://example.com and appends one line to uptime.csv. In the test run, three scheduled runs in a row each added one OK line, and a run against a page that does not exist added one FAIL line with status 404. The log held 7 lines at the end: the header plus 6 checks.
Download the workflow: workflow.json — import it into n8n in step 3. It is the exact file that was imported and run for this guide, including the file path used on the test machine (change it in step 4).
What you need
- Node.js 20.19 to 24.x. n8n requires a Node.js version between 20.19 and 24.x, inclusive (n8n Docs, Install with npm). This guide ran on Node.js 24.22.1.
- n8n 2.46.7, started on your own machine. No account, no credentials — the check is a plain GET request to a public site.
- A terminal for the install and the file setup.
- A site to check. This guide checks
https://example.com. Swap in your own site by changing the URL in the HTTP Request node — the log lines take the URL from that node, so they always name the site that was actually checked.
1. Install and start n8n
Run n8n from the terminal. n8n Docs, Install with npm describes this as the quick way to try n8n locally: the command downloads what it needs and serves the editor at http://localhost:5678.
Command
npx n8n
Open http://localhost:5678. The first start asks you to create an owner account (an email address and a password, kept on your own machine). Note that npm-based installs are deprecated from n8n 3.0, per the same install page — if you are on 3.0 or later, use the Docker install it links to instead, and re-check the node versions above before importing.
2. Make the file
On a default n8n 2 install, the Read/Write Files node can only write under ~/.n8n-files. That is the default value of N8N_RESTRICT_FILE_ACCESS_TO from n8n 2.0 (n8n Docs, Read/Write Files from Disk). Create the folder and the file with its header row now — the workflow appends data rows only, so the header has to exist first.
Command
mkdir -p ~/.n8n-files
printf 'checked_at,url,status,result\n' > ~/.n8n-files/uptime.csv
3. Import the workflow
In n8n, open the workflow menu (the ⋯ at the top right of the canvas) and choose Import from file, then pick workflow.json. The canvas shows nine nodes: three in a line, then the IF node splitting into two branches of three. What each node does:
| Node | Type | Job |
|---|---|---|
| Every hour | Schedule Trigger | Runs the workflow at a fixed time. It works like the Cron utility: this workflow uses the custom cron expression 0 * * * *, hourly on the hour (n8n Docs, Schedule Trigger). You must publish the workflow for the schedule to fire. Note the timezone: the node uses the workflow timezone if set, otherwise the instance timezone, which defaults to America/New_York for self-hosted instances — set the workflow timezone in the workflow settings if the hour should mean your time. |
| Check example.com | HTTP Request | Requests https://example.com with method GET (n8n Docs, HTTP Request). Three settings in Options → Response make the check work: Include Response Headers and Status on, so the next node can read the status code; Never Error on, so a 404 or 500 is passed on instead of failing the node; and Response Format set to Text, since the check is the status code, not the page. With those settings the node outputs statusCode, headers, and body. Leave Never Error off and a down site errors the whole run instead of logging a FAIL line — that is the first trap below. |
| Is it up? | IF | Splits the workflow on one Number condition: {{ $json.statusCode }} is equal to 200 (n8n Docs, If). A 200 takes the true branch; anything else takes the false branch. |
The two Code nodes build the log lines, one per branch, in Run Once for All Items mode (n8n Docs, Code). The URL in each line comes from the HTTP Request node's own parameters, so the line always names the site that was actually checked:
Config — Build OK line, JavaScript
// One CSV line for a site that answered 200.
// checked_at is the run time in ISO format; the URL comes from the
// HTTP Request node's own parameters, so the line always names the
// site that was actually checked.
const item = $input.first().json;
const esc = (v) => {
const s = String(v ?? '');
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
};
const url = $('Check example.com').params.url;
const line = [$now.toISO(), url, item.statusCode, 'OK'].map(esc).join(',');
return [{ json: { line: line + '\n' } }];
Config — Build FAIL line, JavaScript
// One CSV line for a site that did not answer 200.
// The status is the real code the site returned, so a 404 and a 500
// do not look the same in the log.
const item = $input.first().json;
const esc = (v) => {
const s = String(v ?? '');
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
};
const url = $('Check example.com').params.url;
const line = [$now.toISO(), url, item.statusCode, 'FAIL'].map(esc).join(',');
return [{ json: { line: line + '\n' } }];
| Node | Type | Job |
|---|---|---|
| To text file / To text file (FAIL) | Convert to File | Operation Convert to Text File, source field line, with Add BOM off (n8n Docs, Convert to File). One node per branch — n8n needs distinct node names, so the false branch has its own copy. |
| Append OK / Append FAIL | Read/Write Files | Operation Write File to Disk, with the Append option on (n8n Docs, Read/Write Files from Disk). Both branches append to the same file, so the log holds OK and FAIL lines in time order. |
4. Set the file path
Open the Append OK and Append FAIL nodes and set File Path and Name to the absolute path of the file you made in step 2 — for example /home/alex/.n8n-files/uptime.csv. The downloaded file carries the path used on the test machine (/home/hatch/.n8n-files/uptime.csv); change it to yours in both nodes. Keep the file under ~/.n8n-files unless you have changed N8N_RESTRICT_FILE_ACCESS_TO, or the write is refused.
5. Activate it
Click Publish. The schedule only fires on a published workflow — the Schedule Trigger docs say so directly (n8n Docs, Schedule Trigger). The first run lands at the next hour.
6. Prove the FAIL branch works
Do not wait for the site to go down to find out whether the FAIL branch works. Point the check at a page that does not exist, run it once by hand, then put the URL back:
- Open the Check example.com node and change the URL to
https://example.com/this-page-does-not-exist-12345. - Click Execute workflow in the editor. The run should finish with status
success— a 404 is not an error here, because Never Error is on. - Change the URL back to
https://example.comand save.
In the test run for this guide, that manual run appended 2026-11-10T10:12:56.600-05:00,https://example.com/this-page-does-not-exist-12345,404,FAIL to the log. The status in the line is the real 404, and the URL in the line is the page that was actually requested — that is why the Code nodes read the URL from the HTTP Request node instead of hard-coding it.
7. Prove the schedule works
Do not wait an hour to find out whether the schedule fires. Temporarily change the schedule to run every minute, watch the runs, then set it back:
- Open the Every hour node, switch the trigger interval to Custom (Cron), and set the expression to
*/1 * * * *. - Publish the workflow again and wait about three minutes.
- Open the Executions tab. You should see three scheduled runs, all with status
success. - Set the expression back to
0 * * * *and publish again.
In the test run for this guide, three scheduled runs fired at 15:14, 15:15, and 15:16 UTC, all status success. Each one appended one OK line to the log.
8. Check the file
Command
cat ~/.n8n-files/uptime.csv
Output (the whole file from the test run)
checked_at,url,status,result
2026-11-10T10:11:06.753-05:00,https://example.com,200,OK
2026-11-10T10:12:56.600-05:00,https://example.com/this-page-does-not-exist-12345,404,FAIL
2026-11-10T10:14:00.717-05:00,https://example.com,200,OK
2026-11-10T10:15:00.715-05:00,https://example.com,200,OK
2026-11-10T10:16:00.817-05:00,https://example.com,200,OK
2026-11-10T10:16:14.622-05:00,https://example.com,200,OK
The file held 7 lines after the test: the header plus 6 checks — 5 OK and 1 FAIL. The timestamps carry the instance timezone offset (-05:00 here), not UTC, because $now.toISO() uses the instance timezone. The last line is from a final manual run after the schedule was restored to hourly, to confirm the shipped file still logs an OK. Run wc -l ~/.n8n-files/uptime.csv after the next scheduled run — the count should grow by one every hour.
Key result: six runs, six lines. Three manual runs (OK, FAIL, OK) and three scheduled runs (OK, OK, OK), all status success. The FAIL line carries the real 404 and the URL that produced it. The schedule was restored to 0 * * * * and the workflow left published.
Traps
| TRAP | a 404 fails the node by default. The HTTP Request node returns success only on a 2xx code unless Never Error is on. Without it, the first down site errors the run and nothing is logged — the exact case this workflow exists to record. Turn on Never Error and Include Response Headers and Status together, as in step 3. |
|---|---|
| TRAP | the status code is not in the output by default. With the default settings the node returns only the body. The IF node has nothing to compare unless Include Response Headers and Status is on, which adds statusCode to the output. The docs say the same — the node returns only the body unless that option is turned on. |
| TRAP | two nodes cannot share a name. The true and false branches each need a Convert to File node and a Read/Write Files node, and n8n routes by node name. The second copies are named To text file (FAIL) and Append FAIL in the workflow file; giving both branches the same name breaks the wiring. |
| TRAP | the header row must already exist. The workflow appends data rows only. If uptime.csv does not exist, the first run creates it without a header, and the file has no column names. Make the file with the header first, as in step 2. |
| TRAP | the hour means the instance timezone unless you set one. The Schedule Trigger uses the workflow timezone if set, otherwise the instance timezone, which defaults to America/New_York for self-hosted instances. The log timestamps in step 8 carry a -05:00 offset for the same reason. If the workflow fires at the wrong hour, set the timezone in the workflow settings. |
| TRAP | n8n was stopped when this guide was built. The test machine's n8n had received a SIGTERM and was not listening on port 5678, even though it was expected to be running. It was restarted with the same user folder (N8N_USER_FOLDER pointing at the existing n8n-home), which kept both existing workflows and the owner account intact. If the API answers with a connection error instead of a response, check the process before assuming the workflow is wrong. |
End state
A Schedule Trigger fires hourly, an HTTP Request fetches the site with the status code exposed and errors suppressed, an IF node sends 200s down the true branch and everything else down the false branch, and each branch builds one CSV line and appends it to the same file. Six runs logged six lines: five OK, one FAIL with a real 404. The two mistakes that cost time during the build — the HTTP Request node failing on a 404 instead of logging it, and the status code being absent from the output until the full-response option was turned on — are both in the traps above, with the working settings in the workflow file.
Sources
- n8n Docs, Schedule Trigger node — Trigger Rules, the cron examples, the publish requirement, and the timezone rules
- n8n Docs, HTTP Request node — the Response options: Include Response Headers and Status, Never Error, and Response Format
- n8n Docs, If node — conditional splits and the Number comparisons
- n8n Docs, Code node — Run Once for All Items mode, and that the Code node cannot access the file system
- n8n Docs, Convert to File node — Convert to Text File operation and its options
- n8n Docs, Read/Write Files from Disk node — Write File to Disk, the Append option, and the
~/.n8n-filesdefault in n8n 2.0 - n8n Docs, Install with npm — the
npx n8nstart, the Node.js 20.19–24.x requirement, and that npm installs are deprecated from n8n 3.0
Verified 10 November 2026 on n8n 2.46.7 (Node.js 24.22.1) by importing workflow.json and running it: manual execution 17 (15:11:05 UTC, status success) appended an OK line; manual execution 21 (15:12:55 UTC, status success) against https://example.com/this-page-does-not-exist-12345 appended a FAIL line with status 404; three scheduled executions (24, 27, 29 at 15:14, 15:15, and 15:16 UTC, all status success) each appended one OK line; a final manual execution 30 (15:16:13 UTC, status success) appended one more OK line after the schedule was restored to 0 * * * *. The log held 7 lines at the end: the header plus 6 checks. The workflow was left published on the hourly schedule.