Skip to content

Instantly share code, notes, and snippets.

@Floofies
Last active July 2, 2025 11:19
Show Gist options
  • Save Floofies/ce35ea0fe13b848a97fde4ebc48f5545 to your computer and use it in GitHub Desktop.
Save Floofies/ce35ea0fe13b848a97fde4ebc48f5545 to your computer and use it in GitHub Desktop.
TCP Health Monitoring in CloudFlare Worker

TCP Health Checker

This CloudFlare Worker script checks to see if your servers TCP ports are still working, and can test multiple ports and hosts.

Upon health check failures, the script sends an SMS messages via the TextBelt API.

Setup Instructions

  1. Go to TextBelt.com and click Create an API key, then save the new API key for later.
  2. Purchase TextBelt SMS credits for your new API key by clicking Add Funds.
  3. Log into the CloudFlare Dashboard.
  4. Create a new KV Namespace in the Storage & Databases tab.
  5. Create a new Compute Worker in the Compute (Workers) tab.
  6. Go to Bindings and click Add binding, then select KV namespace, select your namespace, and set variable name to alerts.
  7. Go to Settings and add required Variables and Secrets, as outlined below in Required Variables & Secrets.
  8. Click Edit Code in the upper right.
  9. Replace the contents of worker.js with the contents of hc-cf-worker.js, and then click Deploy.
  10. If the script is working, then it should soon return OK or FAIL in the preview window.
  11. Add a new Trigger Event in your Worker's settings tab. Select Cron Triggers and configure the health check schedule.
  12. Scroll to Domans and Routes in the Worker's settings tab and click Disable on both domains to disable public access.

Required Variables & Secrets

Variable Description
hosts A comma-seperated list of socket addresses to be health-checked. Newlines and spaces are allowed. Example: domain.com:80, site.org:2000.
smsNumber The 11-digit phone number which should receive SMS messages when a health check fails.
smsKey Your TextBelt API key.
import { connect } from 'cloudflare:sockets';
// TCP Health Checker
// Checks to see if specific TCP sockets of servers are still working.
const smsURL = "https://textbelt.com/text";
const failMessage = "⚠️ TCP Healthcheck FAILED: ";
const clearMessage = "✅ TCP Healthcheck OK: ";
// Returns an Array of hostnames to be healthchecked, or null of none were found
function getHosts(env) {
if(env.hosts) {
const hostsString = String(env.hosts);
if(hostsString.length)
return hostsString.split(",");
}
console.warn("Aborting: Required nvironment variable 'hosts' is missing or lacks hostnames!");
return null;
}
// Send a text message to the given phone number
function alert(smsNumber, smsKey, message) {
return fetch(smsURL, {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
phone: smsNumber,
message: message,
key: smsKey,
})
});
}
// Resolves with true if TCP connection was successful
async function healthcheck(address, port) {
const tcpSocket = connect({
hostname: address,
port: port,
});
await tcpSocket.opened;
tcpSocket.close().catch(()=>{});
return true;
}
// Sets an alert to tripped state and sends an alert message
async function tripAlert(env, name) {
const alertId = `${name}-tripped`;
if(await env.alerts.get(alertId, "json"))
return;
await env.alerts.put(alertId, true);
await alert(env.smsNumber, env.smsKey, failMessage + name);
}
// Sets an alert to cleared state and sends a clear message
async function clearAlert(env, name) {
const alertId = `${name}-tripped`;
if(!(await env.alerts.get(alertId, "json")))
return;
await env.alerts.put(alertId, false);
await alert(env.smsNumber, env.smsKey, clearMessage + name);
}
// Queues a healthcheck for each given host, and returns an array of Promises which will resolve upon success
function queueHealthChecks(env, hostsArray, sendAlerts = true) {
const checks = [];
let total = 1;
for(const host of hostsArray) {
total++;
const socketAddress = host.trim();
const hostData = socketAddress.split(":");
const address = hostData[0];
const port = Number(hostData[1]);
const queuedCheck = healthcheck(address, port).then(async (success) => {
if(!sendAlerts)
return success;
if(success === true)
await clearAlert(env, socketAddress);
else
await tripAlert(env, socketAddress);
return success;
}).catch(async (err) => {
console.error(err);
if(!sendAlerts)
return err;
await tripAlert(env, socketAddress);
return err;
});
checks.push(queuedCheck);
}
return checks;
}
// Fetch handler for testing purposes, does not send any alerts
async function fetchHandler(request, env) {
const hosts = getHosts(env);
if(hosts === null || !hosts.length) {
return new Response("Aborting: Required nvironment variable 'hosts' is missing or lacks hostnames!", {
headers: { "Content-Type": "text/plain" }
});
}
if(!env.alerts) {
console.warn("Aborting: Required KV Binding 'alerts' is missing!")
return;
}
const checks = queueHealthChecks(env, hosts, false);
const handlers = [];
let currentCheck = 0;
for(const check of checks) {
const idString = `Check #${currentCheck} (${hosts[currentCheck].trim()})`;
currentCheck++;
handlers.push(check.then((status) => {
if(status == true)
return `${idString} OK`;
if(status)
return `${idString} FAILED:\n\t${status}`;
return `${idString} FAILED:\n\tFailed to open TCP socket`;
}).catch((err) => {
console.error(err);
return `${idString} FAILED:\n\t${err}`;
}));
}
const results = await Promise.all(handlers);
return new Response(results.join("\n"), {
headers: { "Content-Type": "text/plain" }
});
}
// Scheduled healthcheck handler which sends alerts upon failure
function scheduledHandler(request, env) {
const hosts = getHosts(env);
if(hosts === null || !hosts.length)
return;
if(!env.alerts) {
console.warn("Aborting: Required KV Binding 'alerts' is missing!")
return;
}
return Promise.all(queueHealthChecks(env, hosts));
}
export default {
fetch: fetchHandler,
scheduled: scheduledHandler,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment