title: "TLS cert expiry watching" description: "Catch a soon-to-expire certificate 14 days before customers do. Two-line install." last_updated: "2026-05-24"

TLS cert expiry watching

Pro+

The worst kind of outage is the kind you scheduled for yourself two years ago and forgot. TLS certificates expire. Renewal automation breaks silently. And then, on a random Tuesday morning, every browser everywhere starts shouting at your users.

This recipe sets up a tls_expiry uptime check that watches a public-facing certificate and alerts you 14 days before it expires.

What you'll end up with

  • One tls_expiry check per host you care about.
  • Daily probing (or however often you want) with a 14-day warning threshold.
  • One alert when the cert crosses the threshold; another if it actually expires.

Step 1: add a TLS-expiry check via the dashboard

  1. Dashboard → Uptime → New check.
  2. Name: api.yourapp.com TLS.
  3. Check type: TLS expiry.
  4. Target: api.yourapp.com:443 (host:port format — port 443 is the usual one but you can use this for any TLS service, including LDAPS, IMAPS, mail submission on 465/587, etc.).
  5. Warn days before expiry: 14 is the default. Bump it to 30 if you want a longer fuse — anywhere from 1 to 365 is accepted.
  6. Probe servers: pick at least one. One is enough; cert expiry is the same fact from every vantage. Picking three doesn't help here and just multiplies probe traffic.
  7. Save.

Step 2 (API alternative)

POST/uptime-checks
Auth: bearer
export TOKEN="bw_..."
 
curl -fsS -X POST https://api.boxwatch.app/uptime-checks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "api.yourapp.com TLS",
    "check_type": "tls_expiry",
    "target": "api.yourapp.com:443",
    "tls_warn_days_before_expiry": 14,
    "timeout_seconds": 10,
    "probe_server_ids": [42],
    "alert_on_cert_expiry": 1,
    "alert_on_down": 1
  }'

Field notes:

  • check_type is tls_expiry (underscore, not hyphen).
  • target is host:port, not just a hostname. Use :443 for HTTPS.
  • tls_warn_days_before_expiry accepts an integer from 1 to 365. Default if omitted is 14.
  • alert_on_cert_expiry is the toggle for the expiring/expired alerts. alert_on_down is separate — it covers the case where the probe can't even establish a TLS connection (cert issuer offline, intermediate chain broken, hostname mismatch).

See the Uptime Checks API reference for every field.

Probe interval and "why daily-ish is enough"

The probe runs on the agent's heartbeat — every minute on Pro/Team/Scale, every 60 minutes on Hobby. Certificates don't change minute to minute, so a high-frequency probe is wasted work. That said, BoxWatch's interval is your plan's push cadence; you can't dial it down per-check in v1. You're effectively probing every minute on Pro and that's fine — it's a single TLS handshake, costs nothing meaningful.

The point is: don't expect this check to catch a freshly issued cert within seconds. Expect it to catch "you have a renewal deadline two weeks from now" within the day.

Two alert types

tls_expiry checks have two alert states:

  1. tls_expiring — fires when days_remaining <= tls_warn_days_before_expiry. This is the warning. You see it 14 days out (or however you configured the threshold). Fires once, when the threshold is crossed; doesn't re-fire daily.
  2. tls_expired — fires when the cert is already past its expiry. By the time this fires, your users are seeing browser errors. Treat it as a "drop everything" alert.

Both ride on the same alert_on_cert_expiry toggle. If you set it to 0, neither fires — which you almost certainly don't want.

In parallel, the same check also raises an ordinary down alert if the TLS handshake fails outright. So the check covers three failure modes:

  • Cert is about to expire (early warning).
  • Cert has expired (red-button warning).
  • TLS connection is broken for any other reason — wrong hostname, missing intermediate, expired root, server offline (down alert).

What you'll see in the dashboard

  • Days remaining: the current value, refreshed each probe cycle.
  • Cert expiry date: the actual notAfter date from the certificate.
  • A history view that shows the days-remaining number ticking down over time. When it crosses your threshold, the row colors yellow; when it crosses zero, red.
  • The cert's subject CN and issuer (handy when you're wondering "wait, which cert is this?").

Step 3: route alerts

Email is on by default. Add Slack at Dashboard → Account → Notifications. See Slack alerts.

The Slack message for tls_expiring reads roughly: "api.yourapp.com TLS — cert expires in 14 days (2026-06-07)." Enough to act on directly.

Pro tip: pair with Let's Encrypt automation

If you're using Let's Encrypt with certbot or acme.sh, your renewal is automated. The interesting failure mode isn't "I forgot to renew." It's "I set up renewal two years ago and the cron stopped running six months ago and I never noticed."

This check catches that case. The Let's Encrypt automation does the renewal; this check verifies that the renewal actually happened by looking at the live cert. If renewal silently failed, you'll see days_remaining ticking down toward zero rather than getting reset to 90 every couple of months — and you'll get a tls_expiring alert 14 days out, giving you a fortnight to fix the renewal automation before things break.

It's belt-and-suspenders. The cost is roughly nothing.

Common gotchas

  • Hostname vs SNI. target is host:port. The host is also used as the SNI value during the TLS handshake. If your server presents a different cert based on SNI (typical for shared hosting), make sure the host in target matches the cert you want to watch.
  • Self-signed and internal CAs. The check verifies the cert it observes against the system trust store of the probe server. A self-signed cert may report days_remaining correctly but also trigger a down because the handshake fails verification. If you're watching internal certs against a private CA, install that CA's root on the probe server.
  • Wildcard certs. A wildcard cert on *.yourapp.com works fine. Just point the check at one specific subdomain — api.yourapp.com:443 — and you'll watch the wildcard's expiry via that endpoint.
  • Hobby plan timing. With a 60-minute push cadence on Hobby, you'll detect an expiring cert within an hour of the threshold being crossed. Pro probes every minute. Either is plenty for cert-expiry use.

See the Uptime docs for the broader picture of how synthetic checks work in BoxWatch.

Was this page helpful?