How-to guide · Operate and monitor
How to monitor Certificate Transparency for your domains
In one sentence
You will have a scheduled check that lists every new publicly trusted certificate for your domains and flags any certificate issued by a CA you did not allow.
- Platforms
- Linux
- macOS
- Windows
- Level
- Practitioner · 5 steps
On this page
Before you start
- The list of registered domains your organisation owns, including parked and mail-only ones.
- The list of CAs you use: the same values as in your CAA records (see How to set CAA records).
- Python 3.8 or later on a machine that can run scheduled jobs, or a CI system with schedules.
- A person or team who will read the alerts.
Why this matters: every publicly trusted TLS certificate is published in Certificate Transparency logs, because Chrome requires Certificate Transparency for publicly trusted TLS certificates (since April 2018). A certificate for your name that you did not order is visible to anyone who looks. A CT monitor makes sure that someone is you.
Steps
1. Choose how you will watch
| Option | Good for |
|---|---|
| A hosted monitoring service (several have free tiers) | Most organisations: alerts by email or webhook, no code |
| Your CA’s or certificate-management platform’s CT alerts | Teams that already use such a platform |
| A small script against a CT search API (below) | Learning, small estates, or feeding alerts into your own tools |
Whichever you choose, the alert must reach a named owner, and a failed check must be as loud as a finding.
2. Search by hand once
Look at what already exists before you automate. In a browser, open https://crt.sh/?q=example.com with your own domain: crt.sh is a free public search over the logs. For scripts, the same search exists as JSON (&output=json).
crt.sh is often overloaded. On 20 October 2026, every request we made returned 502 Bad Gateway. That is why a monitor should not depend on one free service, and why it must report its own failures.
Write down every issuer you see and every name you do not recognise. This is your starting point.
3. Set up a scheduled check
This script uses SSLMate’s Cert Spotter search API, which merges each certificate with its precertificate into one entry and can tell you the issuer’s CAA values. It needs only the Python standard library:
#!/usr/bin/env python3
"""ct-watch.py: list certificates for your domains that appeared in CT logs since the last run."""
import json, os, pathlib, sys, urllib.request
DOMAINS = ["example.com"] # your registered domains
ALLOWED_CAA = {"letsencrypt.org"} # the CA values in your CAA records
STATE = pathlib.Path("ct-watch-state.json") # remembers where the last run stopped
API = "https://api.certspotter.com/v1/issuances"
def fetch(domain, after):
url = (f"{API}?domain={domain}&include_subdomains=true"
"&expand=dns_names&expand=issuer&expand=issuer.caa_domains")
if after:
url += f"&after={after}"
request = urllib.request.Request(url)
if key := os.environ.get("CERTSPOTTER_API_KEY"):
request.add_header("Authorization", f"Bearer {key}")
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
state = json.loads(STATE.read_text()) if STATE.exists() else {}
unexpected = 0
for domain in DOMAINS:
after = state.get(domain)
while page := fetch(domain, after):
for item in page:
issuer = item["issuer"]
known = set(issuer.get("caa_domains") or []) & ALLOWED_CAA
unexpected += not known
print("ok " if known else "NEW", item["not_before"][:10],
issuer["name"], " ".join(item["dns_names"]))
after = page[-1]["id"]
state[domain] = after
STATE.write_text(json.dumps(state))
sys.exit(1 if unexpected else 0)
How it behaves:
- The first run lists every unexpired certificate: your baseline. Later runs list only certificates that appeared since.
- Each certificate is reported once.
okmeans the issuer is one of your allowed CAs;NEWmeans it is not. - The script exits with status 1 when it finds a
NEWcertificate, and crashes with an error when the API cannot be reached. Both make a scheduled job fail visibly. - Without an API key, the service allows a limited number of queries per hour “for personal or evaluation purposes”. For production, get a key and set
CERTSPOTTER_API_KEY, or use a hosted monitor.
We ran it on 20 October 2026 for rfc-editor.org, with an allow-list that deliberately held only Let’s Encrypt (34 lines; a selection):
ok 2026-07-23 C=US, O=Let's Encrypt, CN=YR2 *.demo.rfc-editor.org demo.rfc-editor.org
NEW 2026-08-11 C=US, O=Google Trust Services, CN=WR1 history.rfc-editor.org
NEW 2026-08-12 C=US, O=Google Trust Services, CN=WR1 *.rfc-editor.org rfc-editor.org
ok 2026-08-14 C=US, O=Let's Encrypt, CN=YR1 *.rfc-editor.org rfc-editor.org
NEW 2026-09-09 C=US, O=Google Trust Services, CN=WE1 755a3119.sni.cloudflaressl.com data.diff.rfc-editor.org
ok 2026-09-19 C=US, O=Let's Encrypt, CN=YR1 *.dev.rfc-editor.org *.prod.rfc-editor.org *.staging.rfc-editor.org dev.rfc-editor.org prod.rfc-editor.org staging.rfc-editor.org
ok 2026-10-13 C=US, O=Let's Encrypt, CN=YR2 *.rfc-editor.org rfc-editor.org
The second run, a minute later, printed nothing and exited with 0. The NEW lines are a typical innocent surprise: certificates that a content delivery network obtained for some of the site’s names (note the cloudflaressl.com name). After checking, the owner would add pki.goog to the allow-list, and to the CAA records if they use them.
Schedule it, for example every six hours with cron on Linux:
# m h dom mon dow command
17 */6 * * * cd /opt/ct-watch && python3 ct-watch.py > last.txt 2>&1 || mail -s "CT watch: check last.txt" pki-team@example.com < last.txt
On Windows, use Task Scheduler; in CI, a scheduled pipeline whose failure notifies the owning team.
4. Decide what deserves a question
For each NEW line, and for any ok line you cannot explain, ask:
- Which issuer? A CA you do not use is the strongest signal.
- Which names? Unknown subdomains, wildcards, and names that should only exist internally.
- When? A certificate nobody expected, for example when no renewal was due.
Two lines with the same names and dates often come from one provider issuing an RSA and an ECDSA certificate side by side; Google Trust Services’ WR1 above is an RSA intermediate and WE1 an ECDSA one. Check internally first: most surprises turn out to be a CDN, a SaaS platform or a forgotten test server.
5. Act on a certificate that is not yours
- Keep the evidence: the issuer, serial number and SHA-256 fingerprint. Download the certificate from the CT search and paste it into the decoder below.
- Report it to the issuing CA. Every public CA must publish how to report misuse (Baseline Requirements section 4.9.3) and must investigate within 24 hours (section 4.9.5). If the validation cannot be relied on, the CA must revoke within 24 hours.
- Find out how validation was passed: check your DNS records, web servers, registrar and DNS-provider accounts.
- Tighten your CAA records, so that only your CAs may issue.
Check it worked
- Run the script by hand twice. The first run lists your current certificates; the second prints nothing and exits with 0.
- Prove the alert path: temporarily remove one CA from
ALLOWED_CAA, deletect-watch-state.json, run the job through the scheduler, and confirm the owning team receives the failure. Then restore both.
If something goes wrong
- The API is down or answers 502 or 429. The job fails, which is correct. Retry later, and make sure repeated failures alert as loudly as findings.
- Too many results. Large domains have thousands of names. Watch the registered domain with subdomains included, but compare issuers rather than reading every line.
- Constant
NEWlines from a provider. Add the provider’s CA to the allow-list and to CAA only after you confirm the provider is yours. - Internal host names appear in the logs. Every name in a public certificate is public. Move internal-only names to a private CA.
- Certificates from your private CA never appear. That is expected: CT applies to publicly trusted certificates only.
Why this works
CT-enforcing browsers accept a public certificate only if it was logged, so the logs hold an append-only record of every certificate for your names that those browsers will accept (Certificate Transparency). CT does not stop a bad certificate: domain validation and CAA limit issuance, and monitoring turns a silent mis-issuance into an alert. Once you find one, the CA’s revocation duties give you a path to remove it.
Go deeper
- RFC 9162 and RFC 6962: how CT logs, SCTs and monitors work.
- Chrome’s CT policy: what Chrome requires of every public certificate.
- CA/Browser Forum Baseline Requirements, section 4.9.3: how CAs must accept problem reports.
- SSLMate: Cert Spotter API reference: parameters, pagination and API keys for the search API used above.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.