Experimental TLS/SSL certificate command-line checker
Build the image locally:
docker build -t tlschecker:local .Run the CLI from the container:
docker run --rm tlschecker:local example.comFor the interactive TUI, run the container with a pseudo-TTY and stdin attached:
docker run --rm -it tlschecker:local example.comThe TUI is only used when stdout is an interactive terminal. In non-interactive environments (pipelines, redirected output, CI), the container falls back to the classic text output.
If you are utilizing M1 or higher, please add the option --platform linux/x86_64.
docker run --platform linux/x86_64 josebovet/tlschecker:2.0.1 jpbd.devLinux
curl -LO https://github.com/jbovet/tlschecker/releases/download/v2.0.1/tlschecker-linux.zip
unzip tlschecker-linux.zip
chmod 755 tlschecker
sudo install tlschecker /usr/local/bin/tlscheckerOsx
curl -LO https://github.com/jbovet/tlschecker/releases/download/v2.0.1/tlschecker-macos.zip
unzip tlschecker-macos.zip
chmod 755 tlschecker
sudo install tlschecker /usr/local/bin/tlschecker➜ tlschecker --helpWhen run in an interactive terminal, tlschecker opens a live dashboard by
default: hosts stream in as they are checked, with a fleet list and verdict
tally on the left and a detail pane (expiry lifetime gauge, TLS grade
breakdown, security warnings) for the selected host on the right. Navigate
with j/k (or arrow keys), jump with g/G, quit with q.
Press Enter on a host to open the full certificate explorer: subject and
issuer details, validity dates, serial number and fingerprints, SANs, the
presented chain, embedded SCTs, the grade breakdown with reasons, and scan
results when --scan was used. Scroll with j/k or PgUp/PgDn, and
return with Esc.
Press e on either screen to export the selected host's certificate chain as
PEM. The prompt is prefilled with a filename derived from the host (so
https://example.com:8443 becomes example.com.pem); edit it as you like and
press Enter to write, or Esc to cancel (Ctrl+U clears the field, Ctrl+W
drops a path segment). An existing file is never
overwritten: the prompt stays open with the reason shown beneath the path, so
you can adjust the name and retry without retyping it. This is the interactive
equivalent of --export-pem.
The TUI requires an attached terminal. In Docker, that means using -it so the
container gets a pseudo-TTY and stdin. Without that, Docker will fall back to
the classic text output. The classic text outputs are also used automatically
whenever stdout is piped or redirected, and can always be forced with
-o summary|json|text or --no-dashboard (keeps the configured/-o
format) — so scripts, CI pipelines, and tlschecker -o json | jq behave
exactly as before.
Basic usage:
➜ tlschecker --check-revocation x.com revoked.badssl.com jpbd.dev expired.badssl.com Using custom ports:
➜ tlschecker example.com:8443 secure-service.internal:9443You can specify the port in three ways:
- Using hostname:port format:
example.com:8443 - Using a full URL:
https://example.com:8443 - Using the default port (443) by just specifying the hostname:
example.com
TLSChecker verifies that the presented certificate chain builds to a trusted root in your operating system's trust store — the same authoritative check a browser performs. This runs automatically for every host (it is offline and adds no latency) and is reported as a Trust: line and, in JSON, a trust field:
- Trusted: the chain builds to a system root CA
- Untrusted (reason): the chain does not verify — the reason is the underlying error, e.g.
self-signed certificate,unable to get local issuer certificate, orcertificate has expired - Unknown: no system trust store was available, so trust could not be determined (never reported as a problem)
An untrusted chain adds an UNTRUSTED security warning and caps the configuration grade at C. Because verification happens after inspection, expired and self-signed certificates are still fully reported rather than rejected at the handshake.
Every check also reports (offline, no extra flags) a set of certificate and connection details:
- ALPN — the negotiated application protocol (
h2/http/1.1). - Subject / Authority Key ID — the SKI/AKI identifiers.
- Validation Level —
DV/OV/EV/IV, derived from the CA/Browser-Forum policy OIDs. - Key Usage / Extended Key Usage / Basic Constraints — what the certificate is authorized for.
It also raises informational warnings (which do not affect the grade, so
legitimate private/internal certificates are never penalized) for issuance
problems: a leaf asserting CA:TRUE, a missing serverAuth EKU, a low-entropy
serial, an over-long (> 398-day) validity period, or a chain link whose
signature does not cryptographically verify.
TLSChecker supports comprehensive certificate revocation checking via both OCSP (Online Certificate Status Protocol) and CRL (Certificate Revocation List). These features allow you to verify if a certificate has been revoked by its issuing Certificate Authority.
To enable revocation checking, use the --check-revocation flag:
➜ tlschecker --check-revocation jpbd.devWhen you enable revocation checking, TLSChecker will:
- First check certificate status via OCSP, which provides real-time revocation information
- If OCSP doesn't provide a definitive answer, fall back to CRL checking
- Report the certificate as revoked if either method indicates revocation
The revocation status will be displayed in the output:
- Valid: Certificate is not revoked (confirmed by OCSP or CRL)
- Revoked: Certificate has been revoked (with reason if available)
- Unknown: Revocation status couldn't be determined
- Not Checked: Revocation status was not checked (default when not using the flag)
Example with a revoked certificate:
➜ tlschecker --check-revocation revoked.badssl.comOCSP (Online Certificate Status Protocol):
- Real-time check with the certificate authority
- Faster and more up-to-date than CRLs
- May not be supported by all certificate authorities
CRL (Certificate Revocation List):
- Downloads and checks the CA's published list of revoked certificates
- More widely supported than OCSP
- Lists may be larger and less frequently updated
Note: Revocation checking requires network connections to OCSP responders and CRL distribution points, which adds some latency to the checks.
When using Prometheus integration, the revocation status is included in the metrics:
tlschecker --prometheus --prometheus-address http://localhost:9091 --check-revocation example.comA tlschecker_revocation_status metric is exported with the following values:
- 0 = Not checked
- 1 = Good (not revoked)
- 2 = Unknown
- 3 = Revoked
Every pushed series carries only stable identity labels in the push grouping key: job="tlschecker" and instance="<host>". This is deliberate — a push_metrics PUT replaces the series under its exact grouping key, so putting volatile values (grade, cipher, expired, …) in the key would leave the previous run's series orphaned in the gateway whenever one of those values changed.
Descriptive, changeable metadata therefore lives on a dedicated info metric, tlschecker_certificate_info (constant value 1), with labels cipher, cipher_protocol_version, issuer, and grade (N/A when grading did not run). Join it to the numeric metrics on instance in your queries, e.g.:
tlschecker_days_before_expired * on(instance) group_left(grade, issuer) tlschecker_certificate_info
Every check reports the SHA-256 and SHA-1 fingerprints of the leaf certificate (colon-separated uppercase hex, the same format as browsers and openssl x509 -fingerprint). They appear in text and json output and are useful for certificate pinning and comparison.
Use --export-pem to print the presented certificate chain (leaf first, followed by any intermediates the server sent) as PEM instead of the normal report:
➜ tlschecker --export-pem example.com > example.pemThis works for multiple hosts too; each host's chain is printed in sequence.
By default tlschecker reports only the protocol and cipher that were negotiated for a single connection. With --scan it actively probes the server to enumerate every TLS protocol version (SSLv3 through TLS 1.3) and the cipher suites accepted at each version:
➜ tlschecker --scan example.comBecause this performs many short handshakes (one per version/cipher), it is slower than a normal check. Results are included in text and json output.
--scan implies --grade (the scan is surfaced through the grade, including in the summary table). Scan results feed the analysis: supporting an obsolete/deprecated protocol or accepting a weak cipher produces security warnings (see below) and lowers the grade. This makes the grade reflect the server's full posture rather than only the single negotiated connection (e.g. a server that negotiates TLS 1.3 but still allows TLS 1.0 will no longer score an A).
Every check also reads the leaf's embedded Signed Certificate Timestamps (SCTs) — the signed promises a CA receives when it submits a certificate to Certificate Transparency logs (RFC 6962). Their presence is offline proof that the certificate was submitted to CT, and unlike the --ct-check lookup below it needs no network and is always on:
➜ tlschecker -o text example.com
...
Embedded SCTs (Certificate Transparency): 2
- log cb38f715897c84a1445f5bc1ddfbc96ef29a59cd470a690585b0cb14c31458e7 at 2026-05-18T19:35:22Z
- log d809553b944f7affc816196f944f85abb0f8fc5e8755260f15d12e72bb454b14 at 2026-05-18T19:35:22ZSCTs appear in text and json output only when present. They complement --ct-check: the lookup confirms inclusion against crt.sh, while embedded SCTs prove submission and stay available even when crt.sh is unreachable — so a --ct-check result of Unknown will note any embedded SCTs as offline evidence.
Modern browsers reject publicly-trusted certificates that are not logged in Certificate Transparency logs, and the same logs are what defenders watch for mis-issuance. With --ct-check, tlschecker looks the presented leaf up in public CT logs via crt.sh, matched by its SHA-256 fingerprint (an exact, per-certificate lookup):
➜ tlschecker --ct-check example.comThis performs a network request to an external service (crt.sh), so it is opt-in and adds latency. The result is tri-state, like revocation status:
- Logged — the exact certificate was found in CT. The
text/jsonoutput include a directcrt.shlink; the summary table shows✓. - Not logged — definitively absent from CT. Reported as a security warning (see below) and shown as
✗in the summary. A publicly-trusted certificate that is not logged will be rejected by modern browsers. - Unknown — crt.sh was unreachable or rate-limited, so the status could not be determined (
?in the summary). This is kept distinct from "not logged" so an outage is never mistaken for a problem; the reason is logged to stderr (stdout stays clean for… -o json | jq).
When --ct-check is used, the summary table gains a CT column (✓/✗/?); without it the column is hidden. Being absent from CT does not affect the grade — many legitimate internal/private certificates are intentionally absent from public CT logs, so what that means is left to you rather than the grade.
In addition to revocation and grading, tlschecker surfaces certificate problems as security warnings in all output formats:
- Weak signature algorithm — the certificate or a chain certificate is signed with SHA-1 or MD5
- Incomplete chain — the certificate's issuer was not found in the presented chain
- Invalid chain order — the chain is not in issuer order (each certificate should be followed by the one that issued it)
- Hostname mismatch — the certificate is not valid for the hostname you checked (no matching SAN, with wildcard support, or Common Name)
- Expiring intermediate — an intermediate certificate in the chain has expired or expires within 30 days
- Weak protocol (
--scan) — the server still supports an obsolete (SSLv3) or deprecated (TLS 1.0/1.1) protocol version - Weak cipher (
--scan) — the server accepts a weak cipher suite (RC4, DES/3DES, NULL, EXPORT, anonymous, ...) - Not in CT log (
--ct-check) — the presented certificate was not found in any public Certificate Transparency log
A hostname mismatch, support for an obsolete protocol (SSLv3/TLS 1.0), or acceptance of a weak cipher each cap the TLS grade at C, the same as a self-signed certificate.
If you encounter connection problems, here are some common error messages and solutions:
-
"Cannot resolve hostname"
- Check that the hostname is spelled correctly
- Verify your network and DNS configuration
- Try using an IP address instead if DNS resolution is not available
-
"Connection refused"
- Verify the host is running a TLS service on the specified port
- Check if a firewall might be blocking the connection
- Confirm the service is publicly accessible
-
"TLS handshake failed"
- The server might be using an unsupported TLS version
- There might be an issue with the server's certificate configuration
- Your network might be intercepting the TLS connection
You can use a TOML configuration file to check multiple hosts. Create a file like tlschecker.toml:
hosts = [
"example.com",
"example.com:8443",
"secure-service.internal:9443",
]
# Optional settings
output = "summary"
exit_code = 1
check_revocation = true
grade = false
min_validity = 30
[prometheus]
enabled = false
address = "http://localhost:9091"You can also generate a commented example with tlschecker --generate-config.
Then run TLSChecker with the config file:
➜ tlschecker -c example-tlschecker.tomlSee tlschecker-example.toml for a complete configuration example.
