Troubleshooting¶
Common issues and how to resolve them. If your problem isn't here, the FAQ and Error Handling pages cover more.
Connection problems¶
Symptom: get_cert_info() returns a dict with an error key like ConnectionError or ProtocolDetectionError.
- Confirm the host and port are reachable:
openssl s_client -connect host:443. - Check firewalls, proxies, and DNS. CertMonitor makes a direct TCP connection.
- For non-standard ports, pass them explicitly:
CertMonitor("host", 8443).
from certmonitor import CertMonitor
with CertMonitor("example.com") as monitor:
cert = monitor.get_cert_info()
if isinstance(cert, dict) and "error" in cert:
print(cert["error"], "-", cert["message"])
"Validator not found"¶
A result like {"is_valid": false, "status": "error", "error": "UnknownValidator", "reason": "Validator 'foo' is not implemented."} means the name isn't registered. It counts as an error, so certmonitor check exits 1 and a fleet scan flags the host: a misspelled validator must not pass silently.
- Check spelling against the validator list.
- Remember most validators are opt-in. Enable them with
enabled_validators=[...]. Onlyexpiration,hostname, androot_certificaterun by default.
A validator reports is_valid: false unexpectedly¶
Every failing validator includes a reason explaining exactly why, so read it first:
results = monitor.validate()
for name, r in results.items():
if not r["is_valid"]:
print(f"{name}: {r.get('reason', '(no reason)')}")
Common surprises:
subject_alt_namesreports unsupported. Enable it with a non-emptyalternate_nameslist; it checks extra names only. Usehostnamefor the primary identity.- A validator key is missing. Confirm it is enabled.
validator_argsconfigures a validator but does not enable it. root_certificatereportsSnapshotMismatch. The verified connection returned a different leaf. Refresh and retry; for a rotating backend pool, target a single backend withconnection_host.- A private CA is untrusted. Configure
cafileorcapathfor the trust check. Issuer names alone cannot establish trust. hostnamefails on an IP address. Most certs don't list IPs as SANs. See Using IP Addresses.pq_signature/pq_chainisfalsefor a normal site. This is expected: the cert is classical (EC/RSA), not post-quantum. See Post-Quantum Cryptography.
Inspecting output¶
Validator results and get_cert_info() output are JSON-serializable dictionaries. Raw DER and the internal cert_data snapshot can contain bytes, so do not serialize those directly. Pretty-print to explore:
STARTTLS ports¶
Symptom: a mail, directory, or database port returns an SSLError such as Failed to establish SSL connection with any protocol, or a StartTLSError naming the server's reply.
CertMonitor discovers STARTTLS services on its own, so the usual cause is that discovery could not name the service, or the server refused to upgrade:
- Check what was found. After connecting,
monitor.starttlsholds the discovered protocol, orNoneif nothing was named. - Pin the protocol.
CertMonitor("mail.example.com", 587, starttls="smtp")skips detection and discovery and runs that preamble directly. If this works when discovery did not, the server's greeting is unusual or slow; pinning is the right fix. - Read the server's reply. A
StartTLSErrorcarries it, for exampleSMTP server does not advertise STARTTLSorPostgreSQL server declined SSL. The service is reachable but is not offering TLS on that port. - Try the implicit-TLS port. 465, 993, 995, and 636 speak TLS from the first byte and need no preamble at all.
See STARTTLS Services for how discovery works and when to pin.
SSH vs SSL/TLS¶
CertMonitor auto-detects the protocol. Features like raw DER/PEM and cipher info are SSL/TLS only, so calling them against an SSH host returns a ProtocolError. See Protocol Detection.
Still stuck?
Open an issue with the host/port (if shareable), your Python version, and the full error dict. The error and message fields are the fastest way to diagnose.