We built CertFleet because the alternatives were too expensive.
We are a team of developers at MCS. We needed reliable SSL/TLS and uptime monitoring for our own sites and applications. The tools on the market were either bloated observability platforms or charged too much for simple certificate expiry alerts.
Focused on Reliability
We are developers focused on reliable infrastructure and exceptional developer experience. We created CertFleet to solve a concrete problem we had at MCS: keeping certificates and uptime under control without paying for an entire observability suite.
The product is intentionally narrow: it does SSL/TLS monitoring + uptime checks extremely well, with generous free tier and fair pricing.
44800 Saint-Herblain, France
Independently operated from France (near Nantes).
TLS probe located in Europe (europe-west1).
Clear, minimal, and built on modern primitives.
Everything is designed for reliability, low cost, and transparency. No unnecessary services.
The only component outside Cloudflare is the passive TLS probe (Workers cannot read the full served X.509 certificate).
Signed webhooks (verify with HMAC)
When WEBHOOK_SIGNING_SECRET is set on the worker, every webhook (SSL expiry + uptime down) includes an x-signature header.
This is the hex-encoded HMAC-SHA256 of the exact raw JSON body.
{
"type": "ssl.expiring",
"hostname": "example.com",
"port": 443,
"daysRemaining": 7,
"threshold": 7,
"notAfter": "2026-07-10T12:00:00.000Z",
"issuer": "Let's Encrypt",
"status": "expiring"
} Uptime payload uses type: "uptime.down".
const crypto = require('crypto');
// IMPORTANT: use the raw body string, before JSON.parse
const rawBody = /* exact JSON string from request */;
const receivedSig = req.headers['x-signature'];
const secret = 'your-webhook-signing-secret';
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (receivedSig !== expected) {
// reject — not from CertFleet
return res.status(401).send('bad signature');
} import hmac
import hashlib
secret = b'your-webhook-signing-secret'
raw_body = b'{"type":"ssl.expiring",...}' # exact raw bytes!
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
received = request.headers.get('x-signature')
if received != expected:
raise Exception("Invalid signature - not from CertFleet") # Quick local test (compute what the signature should be)
echo -n "$RAW_JSON_PAYLOAD" | \
openssl dgst -sha256 -hmac "$WEBHOOK_SIGNING_SECRET" | \
awk '{print $2}'
Implementation: workers/api/src/services/alerts.ts (signPayload).
Handy generator: workers/api/test/test-signed-webhook.js
Real dashboard — actual production UI
SSL fleet — VALID / EXPIRING / EXPIRED / ERROR states, CT new-cert badge, per-domain issuer and next-check time.
How SSL monitoring works →
Uptime monitors — UP 38ms / DOWN 503 / PAUSED. Per-monitor interval, latency, next check.
Uptime monitoring details →
SSL history — per-check audit trail: issuer, TLS version, days remaining at each probe.
Uptime history — chronological checks with response time and detected outage windows.
Domain expiry monitors — WHOIS/RDAP registration dates, alert 60/30/14/7 days before.
DNS monitors — track A, MX, CNAME, TXT records. Alert instantly on any change.
Public status page — hosted at /s/your-slug. Shows component health, 90-day uptime bars and incident history. No login required for viewers.
Team & RBAC — invite members by email, assign owner / admin / member / viewer roles. Invitation email sent automatically.
Per-resource alert config — email, Slack, custom webhook. Choose expiry thresholds: 30/14/7/1 days.
REST API keys — create named keys per integration (CI/CD, Grafana, custom scripts). Full API from Starter.
API docs →
Status page management — add components (monitors + certs), post incidents, track resolution.
RGPD / GDPR compliance
MCS, registered under SIREN 106 942 600 (R.C.S. Nantes), EU identification number FR4401.106942600
5Q Avenue des Naudières
44800 Saint-Herblain, France
- Public checker: only the hostname you submit (no personal data, rate-limited + protected by Cloudflare Turnstile).
- Accounts: email address (from password signup or OAuth Google/GitHub), user ID.
- Monitored resources: the hostnames, ports, and full URLs you explicitly add to monitor (chosen by you).
- Alert channels: email addresses, Slack webhook URLs, or custom webhook URLs you configure.
- Technical data: minimal logs for rate limiting, abuse prevention and debugging (IP addresses are not stored long-term for the public checker).
We process data only to provide the monitoring and alerting service you requested (contract performance) and to prevent abuse. We do not sell data or use it for advertising.
- Primary storage: Cloudflare D1 (SQLite) and KV — global but with strong privacy controls.
- Emails sent via Resend (EU-friendly routing when possible).
- Passive TLS probe: short-lived requests only (hostname in, certificate details out). No storage of your traffic or content.
- Certificate Transparency data: fetched from public crt.sh (no personal data involved).
Check history and monitors are kept as long as the resource is monitored or until you delete it from your account. You can delete domains, monitors, or your entire account at any time from the dashboard. Alerts are idempotent (we track what was already sent to avoid duplicates).
You have the right to access, rectify, erase, restrict processing, data portability, and to object. For any request, contact us at the address above or via the support channels in the app. We will respond within legal timeframes.
- Passive TLS reading only — we never scan ports or attempt exploitation.
- Auth sessions stored in KV with short TTL, httpOnly + Secure cookies.
- Public checker protected by Turnstile + per-IP rate limiting in KV.
- No unnecessary third-party analytics or trackers on the landing or app.
Questions about how we build or protect data?