Monitor expiring certificates
The most common first integration: get expiring certificates into whatever you already watch - Prometheus, a Slack channel, a weekly report.
Before building this, note that NextPKI already sends expiry alerts by email and
raises expiry_30d through expiry_1d alerts internally. Build this when you need
the data in your own system, not to reinvent the alerting.
Fetch and filter
Section titled “Fetch and filter”There is no server-side expiry filter in v1. Page through and filter locally.
import os, urllib.request, jsonfrom datetime import datetime, timezone, timedelta
BASE = "https://api.nextpki.com"TOKEN = os.environ["NEXTPKI_TOKEN"]
def get(path): req = urllib.request.Request(BASE + path, headers={"Authorization": f"Bearer {TOKEN}"}) with urllib.request.urlopen(req) as r: return json.load(r)
def all_certificates(page=200): offset = 0 while True: data = get(f"/v1/certificates?limit={page}&offset={offset}") items = data["items"] if not items: return yield from items if len(items) < page: return # short page means last page offset += page
cutoff = datetime.now(timezone.utc) + timedelta(days=30)
expiring = [ c for c in all_certificates() if datetime.fromisoformat(c["not_after"]) <= cutoff and not c["is_revoked"]]expiring.sort(key=lambda c: c["not_after"])
for c in expiring: days = (datetime.fromisoformat(c["not_after"]) - datetime.now(timezone.utc)).days print(f"{days:>4}d {c['common_name'] or c['id']:<45} {c['issuer_dn'][:50]}")Two details that matter. Stop paging on a short page, not on an empty one - otherwise you always make one wasted request. And exclude revoked certificates, or your report will nag about certificates that are already dead.
Decide what deserves an alert
Section titled “Decide what deserves an alert”A naive “expires in under 30 days” list produces noise. Certificates worth waking someone for have three properties:
def actionable(c, seen_recently): if c["is_revoked"]: return False # already dead if c["trust_status"] == "self_signed": return False # an appliance, not a WebPKI cert if not seen_recently: return False # nothing serves it - clean up, don't renew return TrueThe third condition is the one that turns a list into something useful, and it is
also the one you cannot evaluate from the summary alone: fetch
GET /v1/certificates/{id} and look at last_seen_at. A certificate not observed
in weeks is a housekeeping task, not an outage risk.
Prometheus
Section titled “Prometheus”Expose a gauge in seconds-until-expiry and let the alerting rules live where your other rules live.
def render_metrics(certs): now = datetime.now(timezone.utc) lines = [ "# HELP nextpki_certificate_expiry_seconds Seconds until certificate expiry.", "# TYPE nextpki_certificate_expiry_seconds gauge", ] for c in certs: secs = int((datetime.fromisoformat(c["not_after"]) - now).total_seconds()) cn = (c["common_name"] or "").replace('"', '') lines.append( f'nextpki_certificate_expiry_seconds{{id="{c["id"]}",common_name="{cn}",' f'issuer="{c["issuer_dn"][:60]}"}} {secs}' ) return "\n".join(lines) + "\n"Keep the label set small. One label per certificate attribute you might group by is
tempting and turns into a cardinality problem on a large estate - id,
common_name and a truncated issuer are enough.
Scrape your own exporter on your own interval, and refresh from NextPKI hourly. Certificate expiry does not change between scrapes; see rate limits.