Certificates
Certificate Transparency explained: logs, SCTs and monitoring
Certificate Transparency (CT) requires CAs to submit every publicly trusted TLS certificate to public, append-only logs before browsers accept it. Each log returns a Signed Certificate Timestamp (SCT) that the browser checks, and anyone can search the logs for certificates issued to their domains. As of November 2026, Chrome, Safari and Firefox all enforce CT.
Why Certificate Transparency exists
CT exists because any of the many CAs trusted by browsers can issue a certificate for any domain, and before CT nobody would necessarily notice. In 2011 attackers broke into the Dutch CA DigiNotar and issued hundreds of fraudulent certificates, including one for *.google.com that was used to intercept users in Iran. The same year an attacker obtained certificates for major sites through a compromised reseller account at Comodo. In both cases the bad certificates were discovered late, by chance.
Engineers at Google designed CT so that discovery would be systematic instead of lucky. The protocol was published as RFC 6962 in June 2013. It soon proved its value: in 2015 CT logs revealed that Symantec had issued unauthorized certificates for Google domains, the first of several incidents that eventually led browsers to distrust Symantec's roots.
CT does not stop a CA from issuing a bad certificate. It makes sure the certificate becomes public, so that domain owners, browsers and researchers can see it and act.
How a CT log works
A CT log is a public, append-only list of certificates organized as a Merkle tree, which lets anyone verify cheaply that entries were added and never removed. Each certificate is hashed into a leaf, pairs of hashes are hashed together, and the single hash at the top, the tree head, commits to the entire log. The log signs this value as the Signed Tree Head (STH).
root hash (signed as the STH)
/ \
h(0x01 | A | B) h(0x01 | C | D)
/ \ / \
A=h(0x00|e0) B=h(0x00|e1) C=h(0x00|e2) D=h(0x00|e3)
cert 0 cert 1 cert 2 cert 3
Two kinds of proof keep logs honest:
- An inclusion proof shows that a certificate is in the tree. It needs only about log2(n) hashes, so for a log with a billion entries roughly 30 hashes are enough.
- A consistency proof shows that a newer tree head contains everything an older one did, which means the log has not rewritten history.
Logs promise to incorporate each submitted certificate within a Maximum Merge Delay (MMD). Monitors download the logs and watch for interesting certificates; auditors check the proofs.
Tiled logs and static-ct-api
Log operators are moving from the original RFC 6962 HTTP API to static-ct-api, a design in which the log is published as static, cacheable "tiles" of the Merkle tree. This makes logs cheaper to run and easier to mirror. As of November 2026, Chrome's CT log policy requires new logs to implement static-ct-api v1.1.0, with RFC 6962 still allowed for logs that apply before January 1, 2027, and caps the MMD of static-ct-api logs at one minute. Let's Encrypt now runs only static-ct-api logs (Sycamore and Willow, based on its Sunlight software) and has retired its RFC 6962 logs.
Modern logs are also sharded by time: each shard, such as one named for the second half of 2026, only accepts certificates that expire within its window. When a shard's certificates have all expired, the shard can be frozen and eventually turned off.
Precertificates and the SCT
The CA logs a "precertificate" before signing the real certificate, so that the proof of logging can be embedded in the final certificate. A precertificate contains exactly the same data as the final certificate plus a critical "poison" extension (OID 1.3.6.1.4.1.11129.2.4.3) that makes it unusable for TLS.
CA CT logs (2 or more operators)
| 1. precertificate (poison extension) ------>|
| 2. SCT from each log <---------------------| "we will log this within the MMD"
| 3. sign the final certificate with the
| SCT list extension (1.3.6.1.4.1.11129.2.4.2)
v
Web server --- TLS handshake: certificate + embedded SCTs ---> Browser
checks SCT signatures and the CT policy
An SCT contains the log's ID, a timestamp and the log's signature over the certificate data. It is a promise, not a proof of inclusion: the log commits to adding the entry within its MMD, and the ecosystem checks that it actually does. The CA/Browser Forum treats a logged precertificate as evidence that the CA intended to issue the matching certificate, so mis-issuance in a precertificate counts as mis-issuance.
You can see embedded SCTs with OpenSSL. This is the certificate served by letsencrypt.org in November 2026 (signatures shortened):
echo | openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org 2>/dev/null \
| openssl x509 -noout -issuer -dates -ext ct_precert_scts
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Nov 3 13:44:34 2026 GMT
notAfter=Feb 1 13:44:33 2027 GMT
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 4C:63:DC:98:E5:9C:1D:AB:88:F6:1E:8A:3D:DE:AE:8F:
AB:44:A3:37:7B:5F:9B:94:C3:FB:A1:9C:FC:C1:BE:26
Timestamp : Nov 3 14:43:04.256 2026 GMT
Signature : ecdsa-with-SHA256 ...
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 46:A2:39:67:C6:0D:B6:46:87:C6:6F:3D:F9:99:94:76:
93:A6:A6:11:20:84:57:D5:55:E7:E3:D0:A1:D9:B6:46
Timestamp : Nov 3 14:43:06.252 2026 GMT
Signature : ecdsa-with-SHA256 ...
The certificate is valid for 90 days, so Chrome's policy needs SCTs from two distinct logs run by two different operators, and that is exactly what it carries.
Three ways to deliver SCTs
SCTs can reach the browser in three ways, but in practice almost all certificates use the first one.
| Method | How it works | Server changes | Status as of November 2026 |
|---|---|---|---|
| Embedded in the certificate | CA puts the SCT list extension into the final certificate | None | The normal method |
| TLS extension | Server sends SCTs in the signed_certificate_timestamp extension |
Server must obtain and configure SCTs | Supported, rarely used |
| Stapled OCSP response | SCTs inside an OCSP response extension | Requires OCSP stapling | Chrome accepts it only in versions before 148; several CAs, including Let's Encrypt, no longer run OCSP |
What browsers require
All three major browser engines now refuse publicly trusted certificates that lack sufficient SCTs; the exact rules differ by vendor. The numbers below are taken from the official policies as of November 2026.
| Browser | Requirement for embedded SCTs | Enforced since |
|---|---|---|
| Chrome (and Chromium-based browsers) | 2 SCTs from distinct logs if the certificate is valid 180 days or less, 3 if longer; at least one log still Qualified, Usable or ReadOnly; at least two distinct log operators | Certificates issued after April 30, 2018 |
| Apple platforms (Safari and apps using system TLS) | 2 SCTs for 180 days or less, 3 for 181 to 398 days, with a cap on SCTs per operator that count; at least one SCT from an RFC 6962 log | Since late 2018 |
| Firefox | CT log inclusion for all certificates from CAs in Mozilla's root program | Desktop version 135, Android version 145 |
For SCTs delivered through the TLS extension, Chrome requires at least two SCTs from currently accepted logs run by two different operators. Chrome also stops enforcing CT if its copy of the log list has not been updated for 70 days, so that outdated browsers do not break when the set of logs changes.
Shorter certificate lifetimes interact with these rules. Since March 15, 2026 the maximum lifetime is 200 days, so a certificate valid for 181 to 200 days still needs three SCTs in Chrome. From March 15, 2027, when the maximum drops to 100 days, every new publicly trusted certificate falls into the two-SCT tier. See 47-day certificates.
When a certificate fails the policy, Chrome shows NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED. CT is not required for certificates that chain to a root installed locally by a user or an enterprise, which is why a private CA works without logs.
Monitoring CT for your domains
CT only protects you if someone looks, and the cheapest monitor is a periodic search of the logs for your own domain names. Search engines built on CT data, such as crt.sh, index every logged certificate by name. Use % as a wildcard to include subdomains:
curl -s "https://crt.sh/?q=%25.example.com&output=json" \
| jq -r '.[] | [.not_before, .issuer_name, .name_value] | @tsv' | sort -r | head
Our CT search tool runs the same kind of query in the browser. When you review the results, look for:
- Issuers you do not use. If a certificate comes from a CA you have never ordered from, find out why. A CAA record restricts which CAs may issue for your domain in the first place; CT tells you whether that restriction was respected.
- Hostnames you do not recognize. These may be shadow IT, a forgotten staging system or, in the worst case, an attacker with control of a subdomain.
- Unexpected volumes. A sudden burst of certificates can mean an automation loop or a compromised ACME account.
If you find a certificate you did not authorize, contact the issuing CA and ask for revocation; the CA has to investigate under the Baseline Requirements.
Privacy: CT makes hostnames public
Every name in a publicly trusted certificate's SAN list becomes public, usually within minutes of issuance. Names such as vpn.example.com, jenkins.example.com or staging-new-client.example.com reveal infrastructure and sometimes business plans. Automated scanners also watch CT logs and probe new hostnames immediately, which is a problem for web applications that are reachable before their setup is finished.
There are only a few ways to limit this:
- Use a wildcard certificate (
*.example.com) so that only the wildcard is logged. The trade-off is that one private key then protects many hosts. See wildcard certificates. - Use a private CA for purely internal systems; certificates from a locally trusted root are not logged.
- Do not put secrets in hostnames. Treat every name you put in a public certificate as published.
Proposals to redact names in CT logs were discussed during the standardization of CT version 2 but were never deployed by browsers.
RFC 6962 versus RFC 9162
RFC 9162, published in December 2021, defines Certificate Transparency version 2.0 and formally obsoletes RFC 6962, but the deployed ecosystem still uses the version 1 data structures. Browsers check v1 SCTs, CAs embed v1 SCT lists, and static-ct-api keeps the v1 formats while changing how logs are served. Apple even requires at least one SCT from an RFC 6962 log. For practical purposes, "CT" today means RFC 6962 data served through either the original API or static-ct-api.
What CT does not do
CT is a detection system with clear limits. It does not prevent mis-issuance, it does not revoke anything, and it relies on someone actually monitoring the logs. It also says nothing about whether a site is trustworthy; a phishing site's certificate is logged the same way as a bank's. Pair it with CAA records to restrict issuance and with revocation, covered in OCSP and CRL, to act on what monitoring finds.
FAQ
Do I need to configure anything on my server for Certificate Transparency?
Almost never. Publicly trusted CAs embed the SCTs in the certificate itself, so any server that serves the certificate is CT compliant. Delivering SCTs through the TLS extension is optional and rarely used.
Can I keep a subdomain out of CT logs?
Not if it is named in a publicly trusted certificate. You can use a wildcard certificate so that only *.example.com is logged, or use a private CA for internal hosts, because CT is not required for certificates that chain to a locally installed root.
What does NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED mean?
Chrome received a publicly trusted certificate that did not come with enough valid SCTs to satisfy its CT policy. This is usually a CA problem (for example, SCTs from logs that Chrome no longer accepts) or a corporate TLS-inspection device; reissuing the certificate normally fixes it.
Is CT the same as a revocation check?
No. CT makes issuance visible so that problems can be found; it does not tell a browser whether a certificate has been revoked. Revocation uses CRLs, OCSP or browser mechanisms such as CRLSets and CRLite.