Fetch a host's TLS certificate and check it against its CRL(s) — fast, concurrent, and scriptable.
$ sslcheck example.com
SERIAL TARGET ISSUER STATUS EXPIRES NOTE
1AA73FEA257BE3334B9A29552E6F878E example.com CN=Cloudflare TLS Issuing ECC CA 3,O=SS… OK 2026-08-29
brew install --cask auth-it-center/tap/sslchecksslcheck is published into our shared jammy apt-hosted repo on Nexus
alongside other internal packages (not a repo of its own). Get the exact
deb line and signing key from Nexus's repo summary page — the codename and
component below may not match your instance:
curl -fsSL https://packages02.it.auth.gr/repository/jammy/pubkey.asc \
| sudo tee /etc/apt/keyrings/it-auth-gr.asc > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/it-auth-gr.asc] https://packages02.it.auth.gr/repository/jammy jammy main" \
| sudo tee /etc/apt/sources.list.d/it-auth-gr.list
sudo apt update
sudo apt install sslchecksslcheck is published into its own yum-hosted repo on Nexus, yummy
($basearch below resolves automatically to x86_64 or aarch64):
sudo tee /etc/yum.repos.d/sslcheck.repo > /dev/null <<'EOF'
[sslcheck]
name=sslcheck
baseurl=https://packages02.it.auth.gr/repository/yummy/$basearch/
enabled=1
gpgcheck=0
EOF
sudo dnf install sslcheckGrab the tarball, .deb, or .rpm for your OS/arch from the
Releases page and
either install the package directly or extract the tarball and put the
sslcheck binary on your PATH:
tar -xzf sslcheck_Darwin_arm64.tar.gz # or the Linux/x86_64 tarball, etc.
sudo mv sslcheck /usr/local/bin/macOS only: since sslcheck isn't Apple-signed/notarized, a tarball downloaded via a browser (or anything else that sets the quarantine attribute) will get Gatekeeper-blocked the first time you run it — "Apple could not verify 'sslcheck' is free of malware...". Clear the quarantine flag once, after extracting:
xattr -d com.apple.quarantine sslcheck(The Homebrew Cask install path handles this automatically via a postflight hook — this manual step is only needed for direct tarball downloads.)
Every release also publishes checksums.txt and checksums.txt.sigstore.json
— a Sigstore keyless signature bundle produced
by the release workflow itself. There's no public key to fetch or import:
cosign checks the
bundle's certificate against the public Rekor transparency log and confirms
it was signed by this exact GitHub Actions workflow:
cosign verify-blob checksums.txt \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp "^https://github\.com/auth-it-center/sslcheck/\.github/workflows/release\.yml@refs/tags/.*$" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
sha256sum --check --ignore-missing checksums.txtgo install github.com/auth-it-center/sslcheck@latestsslcheck [flags] host [host...]
sslcheck [flags] -f hosts.txt
cat hosts.txt | sslcheck [flags]
sslcheck [flags] -incert cert.pem
Targets may be bare hostnames, host:port, or full URLs:
sslcheck example.com
sslcheck example.com:8443 internal.example.org
sslcheck https://example.com/whateverFlags can go anywhere on the command line, not just before the targets —
sslcheck example.com -json works the same as sslcheck -json example.com.
Short flags also accept an attached value (-c4 as well as -c 4).
Or feed it a list (blank lines and #-comments are ignored):
cat hosts.txt | sslcheck -json -c 20Or read targets from one or more files with -f/-file (comma-separated
for multiple files; combine freely with targets given on the command line):
sslcheck -f hosts.txt
sslcheck -f hosts.txt,more-hosts.txt extra.example.comOr validate certificate(s) already on disk with -incert (comma-separated,
PEM or DER; a chain bundle's leading cert is treated as the leaf), skipping
the network handshake entirely:
sslcheck -incert cert.pem
sslcheck -incert cert.pem,chain.pem| Flag | Default | Description |
|---|---|---|
-c |
10 |
number of concurrent workers |
-timeout |
8s |
per-connection / CRL-fetch timeout |
-port |
443 |
default port when a target doesn't specify one |
-file |
— | read targets from file(s) (comma-separated) |
-f |
— | shorthand for -file |
-incert |
— | validate certificate(s) from local file(s) (comma-separated, PEM or DER) instead of connecting over the network |
-json |
false |
output JSON instead of a table |
-strict |
false |
require a valid TLS chain before inspecting the cert (default: fetch regardless) |
-starttls |
false |
upgrade to TLS via STARTTLS instead of an implicit handshake (SMTP/IMAP/POP3/FTP/LDAP; dialect inferred from the port — see STARTTLS) |
-revoked |
false |
display only targets that are revoked or that errored out |
-noout |
false |
narrow output to just the fields selected below (same table/JSON shape, fewer columns/keys) |
-subject |
false |
certificate subject (extra column; also selectable under -noout) |
-issuer |
false |
certificate issuer (shown by default; also selectable under -noout) |
-serial |
false |
certificate serial number (shown by default; also selectable under -noout) |
-san |
false |
certificate Subject Alternative Names (extra column; also selectable under -noout) |
-startdate |
false |
certificate notBefore date (extra column; also selectable under -noout) |
-enddate |
false |
certificate notAfter date (extra column; also selectable under -noout) |
-status |
false |
target status (shown by default; also selectable under -noout) |
-notes |
false |
result note (shown by default; also selectable under -noout) |
By default the table always shows SERIAL/TARGET/ISSUER/STATUS/EXPIRES/NOTE.
-subject, -san, -startdate, and -enddate append extra columns to that
same table when passed on their own:
$ sslcheck example.com -san
SERIAL TARGET ISSUER STATUS EXPIRES NOTE SAN
1AA73FEA257BE3334B9A29552E6F878E example.com CN=Cloudflare TLS Issuing ECC CA 3,O=SS… OK 2026-08-29 example.com,*.example.comA certificate with multiple SANs gets one per line, right under that row, without disturbing the table's column alignment:
$ sslcheck example.com -san
SERIAL TARGET ISSUER STATUS EXPIRES NOTE SAN
1AA73FEA257BE3334B9A29552E6F878E example.com CN=Cloudflare TLS Issuing ECC CA 3,O=SS… OK 2026-08-29 example.com
*.example.comAdd -noout to narrow the table down to only the fields you select — it's
still a table (and still a -json array if you pass both), just with fewer
columns/keys, mirroring openssl x509 -noout -subject -issuer:
$ sslcheck -noout -subject -issuer example.com
TARGET SUBJECT ISSUER
example.com CN=example.com CN=Cloudflare TLS Issuing ECC CA 3,O=SSL Corporation,C=US-noout with no fields selected shows only TARGET.
Some protocols (SMTP, IMAP, POP3, FTP, LDAP) start out in plaintext and
switch to TLS mid-connection rather than negotiating TLS immediately like
HTTPS does. Pass -starttls to do that upgrade instead of an implicit TLS
handshake; the specific dialect is inferred from the target's port:
| Port | Protocol |
|---|---|
| 25, 587 | SMTP |
| 143 | IMAP |
| 110 | POP3 |
| 21 | FTP |
| 389 | LDAP |
sslcheck -starttls smtp.example.org:587To override the protocol for just one target regardless of -starttls,
append :starttls (force the plaintext-upgrade dance) or :tls (force an
implicit TLS handshake) to that target:
sslcheck smtp.example.org:587:starttls ldaps.example.org:636:tls$ sslcheck -json example.com
[
{
"target": "example.com",
"host": "example.com",
"subject": "CN=example.com",
"issuer": "CN=Cloudflare TLS Issuing ECC CA 3,O=SSL Corporation,C=US",
"serial": "1aa73fea257be3334b9a29552e6f878e",
"sans": [
"example.com",
"*.example.com"
],
"not_before": "2026-05-31T21:39:12Z",
"not_after": "2026-08-29T21:41:26Z",
"crl_urls": [
"http://c.cf-i.ssl.com/ae801ed1c55bb579d79208b0d772acfb8cc3a208.crl"
],
"crl_checked": true,
"revoked": false
}
]Useful for scripting/monitoring — checks all targets, then exits with the highest-severity result across all of them:
| Code | Meaning |
|---|---|
0 |
all targets OK |
1 |
at least one target errored (unreachable, TLS failure, CRL fetch failure, ...) |
2 |
at least one target's certificate is revoked |
-revoked only filters what's displayed — the exit code still reflects
every target checked, revoked/errored or not.