Building CertHalo: SSL certificate monitoring for a world of 47-day certificates
On this page
- Why we built it
- Certificates are about to expire a lot more often
- What people actually do today
- Where CertHalo came from
- What CertHalo does
- Why manage certificates centrally
- How we built it
- Architecture at a glance
- The stack
- The TLS engine
- Things that were harder than they looked
- Security and privacy choices
- How we worked
- Roadmap
- Try it
An expired SSL certificate is one of the most avoidable outages there is, and one of the most common. The site is up, the servers are healthy, the code hasn’t changed, and yet every visitor gets a full-page browser warning, every API client fails, and the payment provider’s webhooks stop arriving. Somebody forgot a date.
CertHalo is our answer to that: a free SSL certificate expiry monitor, built by IncubXperts, that checks your certificates from the outside on a schedule and tells you before anything expires, changes or breaks. This post covers why we built it, how it’s built, and where it’s going.
Why we built it
Certificates are about to expire a lot more often
For years, a certificate lasted a year or more, and “renew the certificate” was an annual calendar entry. That’s ending. In 2025 the CA/Browser Forum, the body of browser makers and certificate authorities that sets the rules, voted to shrink the maximum lifetime of public TLS certificates in steps:
| From | Maximum certificate lifetime |
|---|---|
| March 2026 | 200 days |
| March 2027 | 100 days |
| March 2029 | 47 days |
At 47 days, nobody renews by hand any more. Renewal has to be automated, and automation fails quietly: a firewall change blocks the ACME challenge, a DNS record is cleaned up, a load balancer keeps serving its own old copy, a web server is never reloaded after Certbot renews. The renewal “succeeded” in the log and the site still serves the old certificate.
At the same time, Let’s Encrypt stopped sending expiry reminder emails in June 2025. For many small teams, that email was the only safety net they had.
What people actually do today
When we talked to system administrators and developers we work with, the honest answer to “how do you track certificate expiry?” was usually one of:
- a spreadsheet someone made two years ago,
- a calendar reminder owned by a person who has since left,
- “the CA emails us” (for one of the twenty certificates),
- nothing, and finding out from a customer.
The certificates are spread across load balancers, CDNs, Kubernetes ingresses, appliances and that one IIS box. Nobody has the full list.
Where CertHalo came from
We had run a small tool, SSL Expiry Tracker, for a few years on Firebase. It worked, a handful of teams relied on it, and it was clearly not ready for the shorter-lifetime world. CertHalo is a rewrite from scratch with three goals:
- Check what visitors actually see, from the outside, not what a renewal log claims.
- One list per company, shared by the people responsible, with alerts that don’t depend on a single person’s inbox.
- Free for small use, cheap to run, so it can stay free.
What CertHalo does
- Free SSL checker. Paste a host and get a full report in seconds: grade (A to F), expiry date, the complete chain and whether it’s trusted, hostname match, supported TLS versions, and revocation status (stapled OCSP, then OCSP, then the CRL). A revoked certificate gets an F, because browsers will refuse it.
- Scheduled monitoring. Add your domains (including non-standard ports like
example.com:8443). CertHalo checks every 24 hours on the free plan and every 6 hours on paid plans. - Alerts that mean something. Emails 60, 30, 14, 7, 3 and 1 days before expiry; when a certificate is renewed, changes issuer, stops matching the hostname, becomes untrusted or is revoked; and when a site becomes unreachable (after two failed checks, to avoid noise).
- Teams. A company gets one shared list of domains. Members have roles (Owner, Admin, Member), each person chooses whether they receive alerts, and every alert goes to each person separately with their own unsubscribe link.
- Guides. Practical write-ups for the problems we see most: missing intermediate certificates, Certbot renewals that fail silently, AWS ACM renewal conditions, IIS bindings, Cloudflare SSL modes, preparing for 47-day certificates.
Monitoring 3 certificates is free, forever.
Why manage certificates centrally
If you’re a developer or a sysadmin, here’s the case for one central place:
- Outside-in is the only view that matters. CertHalo connects the way a browser does and reports the certificate that is really being served. That catches the failures internal tooling misses: a CDN or load balancer with a stale copy, a server that wasn’t reloaded, one node in a pool with an old certificate, a missing intermediate that only breaks API clients.
- Automation needs a referee. Certbot, ACM, cert-manager and win-acme all renew automatically, until they don’t. An independent check tells you the morning after, not 30 days later.
- No single point of human failure. Alerts go to everyone in the team who wants them, not to whoever added the domain three years ago. When someone leaves, the domains and their history stay with the team.
- One list for the company. Domains are added once, everyone sees the same status, and nobody is surprised by “oh, we also had that one”.
- It covers more than expiry. A revoked certificate, an issuer change you didn’t plan, a broken chain or a hostname mismatch are all outages in waiting, and they’re alerted the same way.
How we built it
Architecture at a glance
┌─────────────────────────────┐
Browser ── HTTPS ──▶ │ Application Load Balancer │
└──────────────┬──────────────┘
▼
┌─────────────────────────────┐
│ Next.js 16 on ECS Fargate │ website, dashboard, free checker
│ (ARM, 1–4 tasks) │
└──────┬───────────────┬──────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ DynamoDB │ │ Amazon SES │──▶ SNS ──▶ Lambda
│ single table │ │ (email) │ (bounces, delivery logs)
└───────▲───────┘ └───────────────┘
│
EventBridge Scheduler ──▶ Lambda: due checks (every 15 min)
──▶ Lambda: daily plan review & account clean-up
The stack
| Layer | Choice | Why |
|---|---|---|
| Language | TypeScript everywhere | One language from the TLS engine to the UI |
| Repo | npm workspaces monorepo: core (TLS checker), server (data, mail, monitoring), web, worker |
The checker is a pure library; the web app and the Lambdas share it |
| Web | Next.js 16 (App Router, Cache Components, partial prerendering, server actions) | Static marketing pages and guides, dynamic dashboard, one codebase |
| Hosting | AWS ECS Fargate (ARM) behind an ALB | Long-lived Node process for the site, cheap on Graviton |
| Background work | AWS Lambda on EventBridge Scheduler | Checks run in parallel, pay only for what runs |
| Database | DynamoDB, single-table design, on-demand | No servers, pennies at our scale; a GSI orders domains by next check time |
| Amazon SES with a configuration set publishing events to SNS | Bounce and complaint handling, plus delivery timing in CloudWatch | |
| Infrastructure as code | SST v4 (Pulumi underneath) | The whole stack, per stage, in one TypeScript file |
| DNS & bot protection | Cloudflare DNS and Turnstile | Turnstile is invisible for most people and free |
| CI/CD | GitLab CI with AWS CodeBuild-hosted runners | Tests on every merge request; staging on demand; production deploys from tags with a manual gate |
| Local dev | Docker Compose with DynamoDB Local and Mailpit | The full product, emails included, runs offline |
Production runs in one AWS region and staging in another, with deploy roles locked to their own region and resource prefixes. Staging is spun up only when needed. The whole production footprint costs roughly the price of a couple of team lunches a month, which is what lets the free plan stay free.
The TLS engine
The heart of CertHalo is @certhalo/core, a small library on top of Node’s tls module. For each target it:
- Connects with SNI and records the full presented chain, without trusting it blindly.
- Validates the chain against the system roots, checks the hostname, and tries each TLS version separately to report which ones the server accepts.
- Checks revocation: the stapled OCSP response if the server sent one, then the CA’s OCSP responder, then the CRL. CRLs can be large, so we parse them with a small streaming DER walker instead of a general ASN.1 library, and every outbound fetch goes through an SSRF guard (public addresses only, ports 80 and 443).
- Turns the result into findings (expired, expiring, untrusted, mismatch, revoked, weak protocol) and a grade.
The monitor compares each new result with the previous one, so it can tell “renewed” from “issuer changed” from “rolled back to an older certificate”, and only alerts when something actually changed.
Things that were harder than they looked
A few lessons from the build that might save you time:
- Microsoft 365 holds mail from new senders. Our SES logs show emails handed to Microsoft’s servers in about 4 seconds, yet they reached inboxes up to a minute later while Defender scanned them. Gmail took about 1 second. That’s a big part of why we added password sign-in: people shouldn’t wait for an email to get into their own account. Logging every SES delivery event (send, delivery, delay, with the receiving server’s reply) made this measurable instead of anecdotal.
- Rolling deploys cause version skew. During a deploy, ECS briefly runs the old and new containers side by side. A page rendered by one version could post a form to the other, whose server-action IDs don’t match. We fixed it two ways: sticky sessions on the load balancer, and a small client guard that reloads the page once when the server says “unknown action”.
- Behind a load balancer, trust the last
X-Forwarded-Forentry. The first entries come from the client and can be anything; the ALB appends the real address at the end. Our rate limits would otherwise be trivial to dodge. - Count requests where they happen once. With partial prerendering, a page’s dynamic part can render more than once per request. Our first rate limiter on the free checker counted every visit twice; moving the counting into the request proxy, which runs exactly once, fixed it.
- Grace periods beat hard cut-offs. When a plan ends, nothing is removed for 30 days, people get warnings at day 0, 7 and 1, and only then are the newest domains (and people beyond a team’s seats) trimmed. Every rule is computed from dates rather than stored flags, so it can’t drift.
Security and privacy choices
- Sign-in: email and password (scrypt at OWASP-recommended strength, breached passwords refused via Have I Been Pwned’s k-anonymity API), or a one-time 6-digit code by email. Turnstile and rate limits on every form. Sessions can be revoked everywhere at once.
- Unverified addresses get no email. You can use the dashboard immediately after signing up, but CertHalo won’t send a single alert to an address until its owner confirms it. Nobody can use CertHalo to mail a stranger.
- Unsubscribing stops all email, with one-click List-Unsubscribe headers that Gmail and Yahoo require. Bounces and complaints suppress an address automatically.
- Analytics only with consent in Europe, and no tracking pixels or link rewriting in emails.
How we worked
CertHalo is a small-team project, and most of it was written in pair with an AI coding assistant (Claude Code). The pattern that worked for us: write the feature down first (we keep short specs for teams and passwords in the repo), agree on the open questions, then build in small feature branches, each tested against a local stack with DynamoDB Local and Mailpit before it reaches staging. Releases are batches of reviewed features, tagged and deployed with a manual gate.
Roadmap
Here’s what’s coming next. If one of these matters to you, tell us: it moves up the list.
| Feature | What it means for you |
|---|---|
| Paid plans, self-serve | Starter, Pro and Business plans with card payments (Stripe). More certificates, checks every 6 hours, and team seats. Today paid plans are granted by request or perk code. |
| Teams, generally available | Shared domain lists with roles and per-member alerts are built and running; they open to everyone together with self-serve Pro and Business plans. A team activity log for Business is next. |
| Slack and Microsoft Teams alerts | Send a team’s alerts to a channel, so the on-call person sees them where they already work. |
| Webhooks | A signed HTTP callback for every alert, to plug CertHalo into PagerDuty, Opsgenie, ticketing or your own automation. |
| Private network monitoring | A small agent you run inside your network that checks internal certificates (intranet sites, internal APIs, appliances, databases with TLS) and reports results outbound to CertHalo. No inbound firewall holes. |
| Google, Microsoft and Apple sign-in | One-click sign-in, linked to the same account as your password. |
| API | List, add and remove monitored domains from your own scripts and pipelines. |
| Configurable alerts | Choose which alerts you get and how many days ahead, per person. |
| Two-factor authentication | An extra step at sign-in for teams that need it. |
Try it
- Check any site for free: certhalo.com/ssl-checker
- Monitor 3 certificates free, no card: certhalo.com/signup
- Read the guides: certhalo.com/guides
Certificates are about to expire eight times as often as they used to. The day one of yours fails to renew, you’ll want to hear about it from a monitor, not from a customer.
CertHalo is built by IncubXperts.