Skip to content

Detect Changes Between Scans

A fingerprint tells you a certificate changed. compare_snapshots() tells you what changed and whether it matters: a renewal from the same issuer with the same names and key is routine, while a name disappearing from the SANs, a new issuer, a weaker key, or a check that flipped from pass to fail deserves a look.

Try it

Keep the JSON from each run wherever your automation keeps things, then hand two of them to the comparison:

import json
from certmonitor import compare_snapshots

previous = json.load(open("reports/yesterday.json"))[0]
current = json.load(open("reports/today.json"))[0]

report = compare_snapshots(previous, current)
print(report["severity"], report["findings"])
warning ['The certificate was replaced.', 'Validity was extended by 90 days (now expires 2027-01-15).', 'Names removed from the SANs: DNS:api.example.com.']

Any observation shape works: an entry from certmonitor check --json, a scan_hosts() result, or a plain get_cert_info() dictionary. Each carries the parsed certificate, and the first two also carry the validator results, so status changes are compared too.

What it reports

Section Present when Severity
fingerprint The certificate was replaced info
validity notAfter or notBefore moved; extended_days is positive for a renewal info, notice if shortened
sans Names were added or removed notice for added, warning for removed
issuer A different CA signed it warning
subject The subject changed notice
key Algorithm or size changed notice, warning if weaker
status_changes A validator's status changed, or a validator appeared or disappeared between the runs warning if it now fails or errors, notice otherwise
scan_error Either snapshot is a failed scan (it carries error and no results) warning if the current scan failed, notice if only the previous one did

severity is the worst of the above, changed is false when nothing differs, and replaced is true when the fingerprint (or, failing that, the serial number) differs. A replacement whose only other effect is a later expiry is called out as a routine renewal.

A check that stops running is a change too. If yesterday's run had a hostname result and today's does not, the validator was disabled, misspelled, or failed to load, and the report says so instead of staying silent. And when one side is a failed scan, there is no certificate to compare: the report carries the scan's error in scan_error and one finding explaining it, rather than a list of fields that "changed to None".

From the shell

certmonitor check api.example.com www.example.com --json > today.json
certmonitor diff yesterday.json today.json
api.example.com:443  WARNING
  The certificate was replaced.
  Validity was extended by 90 days (now expires 2027-01-15).
  Names removed from the SANs: DNS:api.example.com.
www.example.com:443  SAME

Targets are matched by name. A target present in only one file is reported as a notice. The exit status is 1 when any target reaches the --fail-on severity (warning by default; notice to be stricter), so the command slots into the same cron and CI patterns as certmonitor check. Add --json for the full report.

What replacement does not prove

A changed fingerprint shows that a new certificate is being served, not that a renewal succeeded. Read the validity section and the validator statuses together: a replacement that shortened the validity window, or that made root_certificate fail, is exactly the case this comparison exists to catch.