David Lewin David Lewin
E-Commerce, Research & Ads

Monitoring storefront SSL, checkout latency and API health: StatusCake vs DIY scripts

Disclosure: some links in this guide are affiliate links. If you sign up through them, I may earn a commission at no extra cost to you. How this site makes money.

Short answer

Monitor four things for an online store: certificate expiry on every hostname (and every server behind it), the response time of the cart and checkout pages, the APIs the storefront depends on, and the monitor itself. A cron script in Python can cover the first three from one server, and our tested version alerts Slack only when something changes. What a script on one server can't do is see your store from several regions or confirm a failure from a second location before paging you, and it can't tell you when its own server dies. StatusCake does both: SSL monitors with expiry reminders, uptime checks with confirmation servers, page speed monitors, and a push (heartbeat) test that can watch your cron script. The free plan covers one SSL monitor and 10 uptime checks at 5-minute intervals; Superior is $20.41 a month billed yearly for 50 SSL monitors and 1-minute checks.

Stores rarely go fully down. What happens instead is that one certificate expires on the API subdomain nobody remembers, one load balancer node keeps an old certificate after a renewal, checkout gets slow for shoppers in one region, or the payment provider's API starts returning errors. The homepage stays up and a basic uptime check stays green. This guide covers what to watch on an online store, shows a tested Python script that watches it from cron, and is specific about where a single script stops being enough and a service like StatusCake earns its price.

How this guide was made

StatusCake's plans, intervals, confirmation tests and push monitoring come from its pricing page and knowledge base, and the certificate lifetime schedule from the CA/Browser Forum ballot SC-081 as summarized by several CAs and PKI vendors, all checked in October 2026. The script was tested on a Windows laptop against local TLS servers using certificates from a throwaway test CA (expired, wrong hostname, nearly due, 47-day lifetimes, and one stale node behind a dual-stack name), slow and failing HTTP endpoints, and a mock Slack webhook. It was not run against a live store, and we didn't measure StatusCake's checks ourselves.

1. The short answer

DIY cron scriptStatusCake
Certificate expiryYes, and every IP behind a name, with your own rulesSSL monitors with reminders; 1 on Free, 50 on Superior
Checkout and cart latencyFrom one server, wherever that isUptime and page speed checks from regions you choose
API healthAny request you can script, including authenticated POSTsHTTP checks with status codes and string matching
Confirm before alertingRetries from the same place0 to 3 confirmation tests from other servers in the region
Who watches the watcherNobody, unless you add a heartbeatRuns outside your infrastructure; push tests can watch your scripts
CostA server you already haveFree plan; Superior $20.41 a month billed yearly

Our recommendation is both: the script for checks only you can write, StatusCake for outside-in checks and as the heartbeat that notices when the script stops.

2. What to monitor on a store

LayerWhat to checkWhy it fails silently
Certificates and domainStorefront, www, checkout, API and webhook subdomains; registration expiryAn expired API certificate breaks a headless checkout while the homepage still loads
Shopping pathProduct page, cart, checkout: status code, a string on the page, response timeThe homepage is cached at the CDN; cart and checkout hit the origin
DependenciesStorefront or REST API, payment and shipping APIs, your webhook relay's /statusPages render, but orders fail or never reach fulfilment
The monitor itselfA heartbeat from the cron job to an outside serviceWhen the monitoring server dies, so do the alerts

Hosted platforms change the list. On Shopify, certificates for the storefront and checkout are Shopify's job, so your certificate checks belong on what you run yourself: a headless frontend, API and webhook subdomains, a marketing site. On WooCommerce or any self-hosted stack, every hostname is yours.

3. Certificate lifetimes are shrinking

The usual rule, "alert 30 days before expiry", was made for one-year certificates. Publicly trusted certificates are getting shorter on a fixed schedule, by issue date:

Maximum lifetime of a new public TLS certificate
Maximum lifetime of a new public TLS certificateIssued before 15 March 2026: 398 days; From 15 March 2026: 200 days; From 15 March 2027: 100 days; From 15 March 2029: 47 daysIssued before 15 March 2026: 398 daysIssued before 15 March 2026398 daysFrom 15 March 2026: 200 daysFrom 15 March 2026200 daysFrom 15 March 2027: 100 daysFrom 15 March 2027100 daysFrom 15 March 2029: 47 daysFrom 15 March 202947 days
CA/Browser Forum ballot SC-081. Let's Encrypt certificates are already 90 days. Certificates issued before each date keep their original lifetime.

A fixed 30-day threshold fires constantly on a 47-day certificate and tells you nothing on a 90-day one, where a healthy setup renews with 30 days left anyway. The useful question is "should this certificate have been renewed by now?" ACME clients like Certbot renew when about a third of the lifetime is left, so the script warns below 30% of the certificate's own lifetime, which means renewal has failed for a few days, and raises a critical alert under 7 days whatever the lifetime. In the test, a 47-day certificate with 20 days left was "ok", and one with 10 days left was a warning.

4. The script

One file, standard library only. Certificates are checked on every address the name resolves to, IPv4 and IPv6. HTTP checks take the median of three requests. Slack gets a message only when a check changes state, problems and recoveries, so a broken certificate doesn't post every 5 minutes for a week.

"""Storefront monitor for cron: certificate expiry on every address behind each hostname, checkout and API
latency, alerts to Slack only when a check changes state. Python 3.10+, standard library only.
Usage: storemon.py checks.json   (exit code 2 if anything is critical, for cron mail or a heartbeat)"""
import datetime, json, os, socket, ssl, statistics, sys, time, urllib.error, urllib.request

cfg = json.load(open(sys.argv[1]))
STATE = cfg.get('state_file', os.path.splitext(sys.argv[1])[0] + '.state.json')
ctx = ssl.create_default_context(cafile=cfg.get('ca_file'))  # ca_file only for private CAs and tests


def cert_check(target):
    """Every IPv4 and IPv6 address behind the name: one stale node behind a load balancer is enough to break
    checkout for some shoppers while a check that resolves one address stays green."""
    host, _, port = target.partition(':')
    port = int(port or 443)
    try:
        ips = sorted({ai[4][0] for ai in socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)})
    except OSError as e:
        return [(target, 'critical', 'DNS lookup failed: %s' % e)]
    out = []
    for ip in ips:
        where = '%s [%s]' % (target, ip)
        try:
            with socket.create_connection((ip, port), timeout=10) as raw, \
                    ctx.wrap_socket(raw, server_hostname=host) as tls:
                cert = tls.getpeercert()
        except ssl.SSLCertVerificationError as e:  # expired, wrong name, unknown CA: shoppers see an error page
            out.append((where, 'critical', e.verify_message))
            continue
        except OSError as e:
            out.append((where, 'critical', 'TLS connection failed: %s' % e))
            continue
        start, end = (ssl.cert_time_to_seconds(cert[k]) for k in ('notBefore', 'notAfter'))
        left = (end - time.time()) / 86400
        lifetime = (end - start) / 86400
        # ACME clients renew with about a third of the lifetime left (30 of 90 days). Below 30% the renewal
        # has failed for days, whatever the lifetime: 90-day certs today, 47-day certs by 2029.
        expires = datetime.datetime.fromtimestamp(end, datetime.UTC).strftime('%Y-%m-%d')
        msg = 'expires %s, %.0f of %.0f days left' % (expires, left, lifetime)
        out.append((where, 'critical' if left < 7 else 'warning' if left < lifetime * 0.3 else 'ok', msg))
    return out


def http_check(c):
    """Median of 3 timed requests, so one slow packet doesn't page anyone. Timed to the response headers."""
    times, problem = [], None
    for _ in range(3):
        req = urllib.request.Request(c['url'], c.get('body', '').encode() or None, c.get('headers', {}),
                                     method=c.get('method', 'GET'))
        t = time.perf_counter()
        try:
            with urllib.request.urlopen(req, timeout=c.get('timeout', 15), context=ctx) as r:
                times.append((time.perf_counter() - t) * 1000)
                if c.get('expect') and c['expect'] not in r.read(200_000).decode('utf-8', 'replace'):
                    problem = 'response is missing %r' % c['expect']
        except urllib.error.HTTPError as e:
            problem = 'HTTP %d' % e.code
        except OSError as e:
            problem = 'request failed: %s' % getattr(e, 'reason', e)
    if problem:
        return [(c['name'], 'critical', problem)]
    ms = statistics.median(times)
    state = 'warning' if ms > c.get('max_ms', 2000) else 'ok'
    return [(c['name'], state, 'median %.0f ms (limit %d ms)' % (ms, c.get('max_ms', 2000)))]


results = [r for t in cfg.get('certs', []) for r in cert_check(t)] + \
          [r for c in cfg.get('http', []) for r in http_check(c)]
try:
    prev = json.load(open(STATE))
except (OSError, ValueError):
    prev = {}
changes = [(n, s, m) for n, s, m in results if prev.get(n, 'ok') != s]  # new problems and recoveries only
icon = {'critical': ':red_circle:', 'warning': ':large_yellow_circle:', 'ok': ':white_check_mark:'}
if changes and cfg.get('slack_webhook'):
    text = '\n'.join('%s *%s*: %s' % (icon[s], n, m) for n, s, m in changes)
    req = urllib.request.Request(cfg['slack_webhook'], json.dumps({'text': text}).encode(),
                                 {'Content-Type': 'application/json'})
    urllib.request.urlopen(req, timeout=10).close()  # if Slack fails, state isn't saved and we retry next run
json.dump({n: s for n, s, _ in results}, open(STATE, 'w'), indent=1)
for n, s, m in results:
    print('%-8s %-45s %s' % (s, n, m))
sys.exit(2 if any(s == 'critical' for _, s, _ in results) else 0)

The configuration lists hostnames and requests. The Storefront API check is an authenticated POST that expects a field in the answer, the kind of check that's awkward in most hosted monitors and trivial in a script:

{
  "slack_webhook": "https://hooks.slack.com/services/T000/B000/XXXX",
  "certs": ["shop.example.com", "www.shop.example.com", "api.shop.example.com", "hooks.shop.example.com"],
  "http": [
    {"name": "cart", "url": "https://shop.example.com/cart/", "max_ms": 1500},
    {"name": "checkout page", "url": "https://shop.example.com/checkout/", "max_ms": 2000},
    {"name": "storefront api", "url": "https://shop.example.com/api/2025-10/graphql.json", "method": "POST",
     "headers": {"Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": "PUBLIC_TOKEN"},
     "body": "{\"query\": \"{ shop { name } }\"}", "expect": "\"name\"", "max_ms": 1000},
    {"name": "webhook relay", "url": "https://hooks.shop.example.com/status", "max_ms": 1000}
  ]
}

Run it from cron. The last part sends a heartbeat to an outside service every time the script ran, whether or not it found problems, so a dead server or a crashing script shows up as missing heartbeats:

# Heartbeat: the push URL your monitoring service gives you (StatusCake push test, Uptime Kuma push monitor)
HEARTBEAT_URL=https://push.example.com/your-unique-url
# Every 5 minutes. Exit 0 = all fine, 2 = something critical (already alerted), 1 = the script crashed:
# ping the heartbeat unless it crashed, so a dead box or a broken script raises an alarm somewhere else
*/5 * * * * cd /opt/storemon && python3 storemon.py checks.json >> storemon.log 2>&1; [ $? -ne 1 ] && curl -fsS -m 10 "$HEARTBEAT_URL" > /dev/null

5. Test results

Each certificate came from a throwaway test CA, served by a local TLS server, with the script's ca_file pointing at that CA:

ScenarioExpectedResult
90-day certificate, renewed 10 days ago (IPv4)okPass: 80 of 90 days left
Same hostname on IPv6, a node that missed the renewalwarningPass: 15 of 90 days left
Expired certificatecriticalPass: "certificate has expired"
Certificate for a different hostnamecriticalPass: "Hostname mismatch"
47-day certificate, 20 days leftok (a fixed 30-day rule would warn)Pass
47-day certificate, 10 days leftwarningPass
Cart: one slow request (1.5 s) out of threeok, median under the limitPass: median 79 ms
Checkout at 1.2 s, limit 800 mswarningPass: median 1,226 ms
API health returns HTTP 500critical, exit code 2Pass
Second run, nothing changedNo Slack messagePass
API and checkout fixedOne message with two recoveriesPass

The IPv6 case is the one worth copying. A check that connects to one address, which is what curl or most quick scripts do, would have reported 80 days left while some visitors were getting a certificate due in 15 days. The same thing happens with several servers behind DNS round-robin, or one node that missed a renewal.

6. Why one location isn't enough

A script on one server sees your store from one network. Three things follow:

  • Its problems look like yours. If the server's own network has a bad minute, every check fails at once and you get paged for an outage shoppers never saw. Retrying from the same server doesn't help, because the retry goes through the same network.
  • Your regional problems are invisible to it. A CDN edge returning errors in one region, a DNS provider problem in one continent, or checkout taking 4 seconds from Australia while it takes 300 ms from the data center next to your origin: one vantage point shows one of these, at best.
  • Its latency isn't your shoppers' latency. A monitor in the same region as the origin measures a best case.
Single location vs confirmed multi-location check
Single location vs confirmed multi-location checkCron on one VPS / StatusCakeCron on one VPS: check fails, retry fails through the same network, alert: maybe a real outage, maybe the VPSCron on one VPScheck fails, retry failsthrough the same network,alert: maybe a real outage,maybe the VPSStatusCake: first test fails, 1 to 3 confirmation tests from other servers, alert only if they agreeStatusCakefirst test fails, 1 to 3confirmation tests from otherservers, alert only if theyagree
StatusCake runs the initial test from the monitoring region you pick and, if it detects downtime, runs up to 3 confirmation tests from other servers in that region before marking the site down.

That's the part worth paying for. A script is better at checks that need logic: every IP behind a name, authenticated API calls, lifetime-aware thresholds. An outside service is better at answering "is it down for shoppers, or just for us?" and at still being up when your server isn't.

7. Setting up StatusCake for a store

  1. SSL monitors on each hostname you're responsible for. Reminders are customizable, with three alerts before expiry; with 90-day or shorter certificates, set them close to expiry, for example 21, 10 and 3 days, so they mean "renewal failed" rather than "normal life cycle".
  2. Uptime tests on the product page, cart and checkout, with a string match on something that only appears when the page works, like the checkout button label, not just a 200. Add /status of your webhook relay or other backend services.
  3. Confirmation tests set to 1 to 3, so one bad network path doesn't page anyone.
  4. Page speed monitors on the cart and checkout, for trends rather than alerts: every 15 minutes on Superior, every 5 on Business.
  5. A push test for the cron script: StatusCake gives you a URL, and alerts if no push arrives within the period you set. Push monitoring is included on free and paid plans.
  6. Domain monitors for registration expiry. An expired domain takes everything down at once, and renewal emails go to whoever registered it years ago.

8. What StatusCake costs

PlanPriceUptimeSSLPage speedDomains
Free$010 monitors, every 5 min1, checked daily1, daily1
Superior$20.41/mo yearly, $24.49 monthly100, every 1 min50, every 30 min15, every 15 min50
Business$66.66/mo yearly, $79.99 monthly300, every 30 s100, every 10 min30, every 5 min120

Prices from StatusCake's pricing page, October 2026. For one store, Free covers the uptime checks and the heartbeat, but only one SSL monitor; Superior is the plan where every hostname gets its own SSL monitor and 1-minute checks on checkout.

9. Our opinion: trade-offs

  • Don't choose; split the work. Run the script for logic-heavy checks (every IP, authenticated APIs, lifetime-aware certificate rules) and use StatusCake for outside-in uptime, confirmation and the heartbeat. The script's blind spots are exactly what an outside service covers.
  • Alert on state changes, not on state. A monitor that repeats itself every 5 minutes gets muted, and then it's worse than nothing.
  • Thresholds should follow certificate lifetime. 30 days made sense for one-year certificates. With 90 days now and 47 days by 2029, alert on "renewal is overdue", not on a fixed number of days.
  • Measure the path that makes money. Homepage checks catch the least interesting failures. Cart, checkout and the APIs behind them are where a slowdown costs orders.
  • Why StatusCake is our pick here. It covers SSL, domain, uptime with confirmation, page speed and push heartbeats in one place, and the free plan is enough to start with the heartbeat and the most important uptime checks.

Related: UptimeRobot vs custom scripts for checkout and scraper monitoring, hosting a webhook relay on Railway or a VPS and protecting WooCommerce checkout from carding bots.

10. Tools

Top picks

ToolRoleWhy it fits
StatusCakeOutside-in monitoring and the heartbeat for your scriptsUptime tests with confirmation servers, SSL and domain monitors, page speed monitors and push tests. Free plan with 10 uptime monitors; Superior at $20.41 a month billed yearly.

Alternatives: free and official tools

We don't earn anything from these links.

  • Uptime Kuma: open-source, self-hosted monitoring with HTTP keyword checks, certificate expiry notices, push monitors and Slack alerts. It shares the single-location limit, so run it somewhere other than the servers it watches.
  • Python ssl module: getpeercert() and cert_time_to_seconds(), used by the script.
  • Let's Encrypt integration guide: renewal timing and why to renew automatically, well before expiry.
  • crt.sh: Certificate Transparency search; lists every certificate issued for your domains, including hostnames you forgot about.
  • Slack incoming webhooks: how to get the URL the script posts to.

11. Pitfalls

  • Checking one IP per hostname. A stale node behind a load balancer or on IPv6 stays invisible.
  • Disabling verification to read the expiry date. A check with verification off reports "60 days left" on a certificate browsers reject for the wrong hostname or an incomplete chain.
  • A fixed 30-day threshold. Noisy on short certificates, late on long ones. Use a share of the lifetime plus a hard floor.
  • Monitoring only the homepage. It's usually cached at the CDN and the last thing to fail.
  • The monitor on the server it monitors. When the server dies, so do the alerts. Add a heartbeat to an outside service.
  • Alerting every run. Repeated alerts get muted. Alert on changes, including recoveries.
  • Forgetting the domain itself. Registration expiry takes down the store, email and every certificate renewal at once.
Bottom line

Watch certificates on every hostname and every IP behind it, with thresholds tied to the certificate's lifetime, time the cart, checkout and APIs, and alert only on changes. A standard-library Python script does that from cron and passed every scenario in our test. What it can't do is confirm a failure from another location or notice its own death. StatusCake covers both, with confirmation tests, SSL and domain monitors, and a push test that watches the script, and its free plan is enough to start.

David Lewin
David Lewin

Founder of StealthScale.io and e-commerce infrastructure engineer writing field-tested playbooks on proxies, cloud VPS, anti-detect browsers and SEO.