Pingdom Wanted $70 a Month to Watch Sites I Already Own
I run three production Next.js sites off a Mac Studio in my apartment — dajai.io, hellcatblondie.io, sovereignagiasi.com — all fronted by a single Cloudflare tunnel, each one a launchd job on its own port. When one of them falls over, it's not a dashboard number that suffers. It's revenue. A dead link funnel means a fan clicks through to nothing.
So the obvious move was to rent a monitor. Pingdom, Datadog Synthetics, Better Uptime — they all do the same thing: hit your URL every N minutes and text you when it 500s. The cheapest tier that covered my subpaths and SSL alerts ran about $70/month. For a curl loop. I already own the machine, the domains, and the shell. I built my own watcher in an afternoon, and it's been running every 10 minutes for months. Here's exactly how it's wired.
The Probe: One Config File, One Script
The whole system reads from one JSON file — ~/daj-ai-hub/mythos/config/domains.json. Each target is a URL, an expected status, and the launchd label of the service that serves it:
{
"name": "hellcatblondie.io/links",
"url": "https://hellcatblondie.io/links",
"expectStatus": 200,
"purpose": "link funnel — revenue-critical",
"launchdLabel": "io.hellcatblondie.nextjs"
}
Right now that file has 23 targets: the three apex domains, plus the subpaths that actually earn — the link funnel, the seller-intake and buyer-concierge CTAs on privatesell4u.com, a Fanvue webhook health endpoint, a Stripe storefront. The point is you don't just watch the homepage. You watch the pages that break silently — a route that 404s after a bad deploy while the apex still returns 200 and everything looks fine.
The probe script (web-watch) walks that array and does three checks per target:
- HTTP status — fetch the URL, compare against
expectStatus. Anything that isn't a match, or a connection refused, is a failure. - Latency — flag anything slower than
latencyWarnMs(5000ms in my config). Slow is the early warning before dead. - SSL days-remaining — pull the cert off the TLS handshake, compute days until
notAfter, warn undersslExpiryWarnDays(14). This one has saved me twice. A cert doesn't page you when it's about to lapse; it just quietly expires at 3am and every browser throws a scary red wall.
Checking the Cert Yourself Is Three Lines
You don't need a library for the SSL piece. Node's tls module hands you the cert on connect:
const socket = tls.connect(443, host, { servername: host }, () => {
const cert = socket.getPeerCertificate();
const daysLeft = Math.floor(
(new Date(cert.valid_to) - Date.now()) / 86400000
);
socket.end();
if (daysLeft <= 14) warn(host, `cert expires in ${daysLeft}d`);
});
Cloudflare handles my edge certs so they auto-renew, but the check still matters — because a tunnel misconfig or an origin cert drift shows up here first, days before it becomes an outage.
Verify the PID Is Actually Alive
Here's the part rented monitors can't do: they only see your site from the outside. They can't tell you which of your services died. Because my config carries the launchd label per target, the watcher does a second, local check — is the process even running?
launchctl print gui/$(id -u)/io.hellcatblondie.nextjs \
| grep -q 'state = running' || echo "DOWN: hellcatblondie.nextjs"
Now a failure isn't "the site is down." It's "io.hellcatblondie.nextjs is not running, here's the label to kickstart." That's the difference between a 2am alert you can act on from your phone and one that just ruins your sleep. My thresholds config also carries an autoHealConsecFailures: 3 with a 30-minute cooldown — three strikes on the same service and the watcher can launchctl kickstart it once before it bothers me at all. Most blips self-heal and I never hear about it.
The Cadence: launchd, Not cron
The whole thing runs as a launchd agent, ai.dajai.mythos.web-watch, on a StartInterval of 600 seconds. I use launchd over cron for the same reason I run everything else on it: it survives reboots, it logs stdout/stderr to files I choose, and if the watcher itself crashes, launchd doesn't care — the next 10-minute tick fires clean. There's no daemon to keep alive, no supervisor for the supervisor. The OS is the supervisor.
Ten minutes is the sweet spot. Every minute is noise and wasted wakeups; hourly is too slow when a bad deploy is bleeding clicks. At 600 seconds, worst case I find out about an outage in ten minutes, which is faster than I'd notice it myself.
Only Page Me When It's Real
The last piece is the one that makes it livable: it stays silent unless something is actually wrong. No "all systems green" spam, no daily digest. When a check fails, the watcher POSTs to a local notify bridge on :8500 that pipes straight into iMessage, so the alert lands on my phone as a normal blue-bubble text:
DOWN hellcatblondie.io/links — HTTP 502
service io.hellcatblondie.nextjs not running
That's it. A URL, a reason, and the service label to restart. No app to install, no dashboard to check, no login. If my phone is quiet, the fleet is healthy. That inverted default — silence means good — is the whole reason I stopped renting this. A monitor that only speaks when it matters is worth more than one with a beautiful dashboard I never open.
Total cost: $0/month on hardware I already run. Total dependencies: Node's stdlib, launchd, and a webhook to my own phone. You own the sites. You should own the thing that watches them.
FAQ
Why not just use UptimeRobot's free tier?
Free external monitors cap your interval, limit how many endpoints you can watch, and — critically — can only see your sites from outside. They can't check a launchd PID, tail your local logs, or kickstart a dead service. Once your monitor lives on the same box as your services, it can diagnose and self-heal, not just alert.
How do I get alerts to my phone without a paid service?
I run a tiny local HTTP bridge on port 8500 that receives a POST and relays it into iMessage via AppleScript, so alerts arrive as normal texts. If you're not on macOS, a Telegram bot or ntfy.sh topic does the same job for free — the watcher just needs one endpoint to POST a line of text to when a check fails.
Won't a self-hosted monitor miss it if the whole machine goes down?
Yes — a watcher on the same box can't report that the box is dead. That's the one job worth outsourcing. I pair my local fleet-watcher with a single free external ping against the apex domain as a dead-man's switch: if that outside check goes quiet, the whole machine is gone. Local watcher for granular per-service health, one external ping for total-blackout insurance.
How often should it run?
Ten minutes (launchd StartInterval 600) is my default and it's held up well. Faster than that is mostly wasted wakeups and alert noise; slower and a bad deploy bleeds traffic before you notice. Tune it to how fast you can actually respond — there's no point checking every 60 seconds if you can't act for an hour anyway.