Most TLS outages and "certificate not trusted" tickets are diagnosed slowly because teams guess instead of looking at what the server actually sends. The OpenSSL command line gives security and operations teams a direct view of the handshake, the certificate list, the chain a client would build and the exact verification error. This guide covers the commands that matter for defenders: s_client, x509, verify, req and pkcs12, how to read their output, how to map common client errors such as Java's SSLPeerUnverifiedException to a root cause, and how to keep OpenSSL itself patched. It is written for security engineers, SREs and PKI owners. For the protocol itself, read the How TLS Works Guide first.
This is an independent guide from NHI Mgmt Group, based on the OpenSSL project's public documentation as of November 2026. It is not sponsored or reviewed by the OpenSSL project. Product features change, so confirm settings against the OpenSSL project's current documentation.
Key takeaways
- s_client shows what was sent, not what is trusted.
-showcertsprints the certificates in the order the server sent them and the manual states plainly that this is not a verified chain. - Verification does not fail the connection by default. With
-verifythe handshake continues after errors so you can see every problem; add-verify_return_errorwhen you want a test to fail hard. - Always send the right SNI and check the name. Use
-servernameand-verify_hostname, otherwise you can inspect the wrong virtual host and miss a hostname mismatch. - Most chain failures are one of four errors. Missing intermediate, untrusted root, expired certificate and hostname mismatch account for the bulk of tickets, and each has a distinct OpenSSL message.
- Track the OpenSSL release line you run. OpenSSL 3.0 and 1.1.1 are out of public support; 3.5 is the current LTS with support to 8 April 2030, according to the project's release strategy page.
What the tool does
OpenSSL is an open source toolkit for TLS and general purpose cryptography, licensed under the Apache License 2.0. The openssl binary exposes subcommands that wrap the library. For defenders five of them do almost all of the work.
| Subcommand | What it does | Options defenders use most |
|---|---|---|
s_client | Acts as a TLS client against any host and port, prints the handshake, certificates and session details | -connect, -servername, -showcerts, -verify_return_error, -verify_hostname, -starttls, -status, -alpn, -brief |
x509 | Parses and prints a certificate, checks expiry and names | -noout, -text, -subject, -issuer, -dates, -enddate, -serial, -fingerprint, -ext, -ocsp_uri, -checkend, -checkhost, -checkip |
verify | Builds and validates a chain offline against a trust store | -CAfile, -CApath, -untrusted, -purpose, -verify_hostname, -attime, -x509_strict, -crl_check |
req | Creates and inspects certificate signing requests (CSRs) | -new, -key, -subj, -addext, -text, -verify, -noout |
pkcs12 | Builds and parses PFX/PKCS#12 bundles of key, certificate and chain | -export, -in, -inkey, -certfile, -info, -nokeys, -legacy |
File formats (PEM, DER, PFX, JKS) are covered in the Certificate and Key File Formats guide, so this page assumes you know which one you hold.
Defender use cases
1. See exactly what a server presents
The baseline command is openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null. The manual describes the defaults that matter: if -servername is missing, SNI is populated from the -connect name when it looks like a DNS name (the default since OpenSSL 1.1.1), so a target given as an IP address does not get the hostname you meant. On shared load balancers or CDNs that means you may be looking at a default certificate, not the one real clients get. Redirecting standard input from /dev/null closes the session cleanly instead of waiting for input.
Read three parts of the output: the Certificate chain block (each entry shows s: subject and i: issuer, so you can see whether every issuer is followed by its own certificate), the Server certificate block and the final Verify return code line. A return code of 0 (ok) means the chain built to a trust anchor in the trust store OpenSSL used, which may not be the trust store your application uses.
2. Test non-HTTPS services
Mail, directory and database services often negotiate TLS after a plaintext greeting. -starttls supports the keywords smtp, pop3, imap, ftp, xmpp, xmpp-server, irc, postgres, mysql, lmtp, nntp, sieve and ldap. For example openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com. Implicit TLS ports (465, 993, 636) need no -starttls.
3. Check expiry before it becomes an outage
openssl x509 -in cert.pem -noout -enddate prints notAfter. For scripts, -checkend 2592000 exits non-zero if the certificate expires within 30 days (the argument is in seconds), which makes it a simple monitoring probe when piped from s_client. With shorter certificate lifetimes coming, read the Certificate Expiry Outages and 47-Day TLS Certificates Guide.
4. Validate a chain offline
openssl verify -CAfile root.pem -untrusted intermediates.pem -verify_hostname app.example.com leaf.pem reproduces what a client does without touching the network. -attime checks validity at a chosen time (useful to prove a certificate will fail after a migration date), and -purpose sslserver catches certificates issued with the wrong extended key usage.
5. Inspect and repair PKCS#12 bundles
openssl pkcs12 -in bundle.pfx -info -nokeys lists the certificates without exporting the key. OpenSSL 3.x creates PKCS#12 files with AES-256-CBC and PBKDF2 by default; the -legacy option loads the legacy provider and uses RC2 or 3DES for certificates and 3DES for keys. Older Java and Windows importers sometimes reject the modern format, so -legacy is a compatibility switch to use deliberately, never a default.
6. Confirm CSR content before submission
openssl req -in request.csr -noout -text -verify checks the CSR signature and shows the subject and requested extensions. Confirm the subjectAltName list, because modern clients match names against SAN entries, not the subject common name.
Diagnosing chain and hostname validation errors
Application errors are usually a wrapper around one of a handful of X.509 verification results. The OpenSSL error strings below come from the library's documentation of X509_STORE_CTX_get_error.
| OpenSSL message | Usual root cause | How to confirm | Fix |
|---|---|---|---|
| unable to get local issuer certificate | Server does not send its intermediate, or the client trust store lacks the root | s_client -showcerts: last i: issuer has no matching certificate | Configure the full chain file on the server; update the client trust store if the root is missing |
| unable to verify the first certificate | Only the leaf is sent and its issuer cannot be found | Chain block shows a single entry | Serve leaf plus intermediates (not the root) |
| self-signed certificate / self-signed certificate in certificate chain | Private CA or interception proxy root not trusted by this client | Issuer equals subject at depth 0, or a private root appears in the sent list | Distribute the private root to the right trust store; see the Certificate trust stores and root CA management guide |
| certificate has expired / certificate is not yet valid | Expired leaf or intermediate, or wrong system clock | x509 -noout -dates on each certificate; check NTP on the client | Renew and redeploy; fix time sync |
| hostname mismatch | Name not in SAN, wrong SNI, or default certificate served | s_client -servername host -verify_hostname host; x509 -noout -ext subjectAltName | Reissue with correct SANs or fix the virtual host and SNI routing |
| unsuitable certificate purpose | EKU lacks serverAuth (or clientAuth for mTLS) | x509 -noout -ext extendedKeyUsage | Reissue from the correct template |
Java's SSLPeerUnverifiedException: the Java SE documentation describes it as indicating that the peer's identity has not been verified, thrown when the peer could not identify itself, for example no certificate, a cipher suite without authentication, or no peer authentication during the handshake. In practice it often surfaces after an earlier trust failure, or when code asks for peer certificates on a session that did not authenticate. Reproduce the connection with s_client using the same hostname, then compare the chain with the Java trust store (keytool -list -cacerts), because Java does not use the operating system store by default.
Mutual TLS: when the server requests a client certificate, s_client prints Acceptable client certificate CA names. Supply -cert and -key to test. Client certificates are workload identities and should be inventoried like any other credential; see the mTLS Guide.
Deployment and safe use
- Know which OpenSSL you are running.
openssl version -ashows the version, build date andOPENSSLDIR(where the default trust store and configuration live). Operating systems, language runtimes and containers often bundle different copies. - Use a controlled trust store for tests. Pass
-CAfileor-verifyCAfileexplicitly so results match the client you are debugging rather than the host's default store. - Never "fix" errors by disabling verification. The manual notes that plain
-verifynever fails the connection; scripts and health checks should use-verify_return_errorso they fail when validation fails. - Protect private keys. Keep keys out of shell history and ticket attachments, prefer
-nokeyswhen only certificates are needed, set file permissions to the owning service account, and use pass phrase options such as-passin file:orenv:rather than typing secrets on the command line, where they appear in process listings. - Treat pasted output as sensitive. Certificates are public, but
s_clientoutput can include session tickets and internal hostnames; redact before sharing externally. - Stay on a supported release line. The OpenSSL release strategy states that feature releases ship every April and October, an LTS is designated every two years from 3.5 with at least five years of support, and non-LTS releases after 3.5 are supported for 13 months. As checked on 8 November 2026, 3.5 (LTS) is supported to 8 April 2030, 4.0 to 14 May 2027 and 4.1 to 23 November 2027, while 3.0 and 1.1.1 receive only paid extended support.
Detection and hardening value
OpenSSL is also a dependency you must patch. Its vulnerability page rates issues Low, Moderate or High. Apply OpenSSL security updates promptly from the OpenSSL vulnerabilities page, and check fixed versions per release line rather than assuming one upgrade covers every branch. Recent releases also change defaults that affect testing: OpenSSL 3.5.0 made hybrid post-quantum groups preferred with default key shares X25519MLKEM768 and X25519, and 4.0.0 added authority key identifier checks under -x509_strict. See the Hybrid Post-Quantum TLS Key Exchange Guide.
| Measure | What to watch | Target |
|---|---|---|
| Certificates expiring within 30 days | x509 -checkend probe per endpoint | Zero unplanned, every one ticketed |
| Endpoints serving an incomplete chain | s_client -verify_return_error with a minimal trust store | Zero |
| Hostname mismatches on public names | -verify_hostname sweep from the asset inventory | Zero |
| Hosts on unsupported OpenSSL lines | SBOM or package inventory for 1.1.1 and 3.0 | Falling each quarter, with exceptions documented |
| Time to patch High OpenSSL advisories | Advisory date to fleet coverage | Within your critical patch SLA |
Framework mapping
| Framework | Reference | How OpenSSL work supports it |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SC-8 Transmission Confidentiality and Integrity; SC-17 Public Key Infrastructure Certificates; SC-23 Session Authenticity | Evidence that TLS is configured and certificates validate |
| NIST SP 800-53 Rev. 5 | SC-12 Cryptographic Key Establishment and Management; SI-2 Flaw Remediation | Key handling for PKCS#12 and CSRs; patching OpenSSL itself |
| NIST CSF 2.0 | PR.DS-02 (data in transit protected); ID.AM-02 (software inventories) | Endpoint checks; tracking OpenSSL versions |
| MITRE ATT&CK | T1557 Adversary-in-the-Middle; T1588.004 Digital Certificates | Strict validation is what defeats interception with untrusted or attacker-obtained certificates |
Alternatives
| Tool | Choose it when | Trade-off |
|---|---|---|
| OpenSSL CLI | You need the raw handshake, a specific error or offline chain checks | Output is verbose and options vary between versions |
| testssl.sh (GPLv2) | You want a full protocol, cipher and known-flaw review of one service, including STARTTLS, with JSON or CSV output | Slower; tests many handshakes against the target, so agree scope first |
| SSLyze (Python library and CLI) | You want scans in CI/CD against Mozilla's recommended configurations or at scale | Python dependency; less useful for a single odd error |
| keytool (Java), certutil (Windows) | The problem is inside a Java keystore or the Windows certificate store | Platform specific; do not show the network view |
Small teams: a cron job running s_client and x509 -checkend against a list of hostnames covers expiry and chain monitoring. Large estates: feed the same checks from the asset inventory and certificate lifecycle tooling, and keep OpenSSL for root cause analysis. See the Machine Identity, PKI and Certificate Lifecycle Guide.
Common failure patterns
- Testing without SNI: connecting by IP or without
-servernamereturns a default certificate and a false all-clear. - Reading -showcerts as proof of trust: the sent list can be complete, out of order or contain an unneeded root; only the verify result tells you whether a client will trust it.
- Different trust stores: OpenSSL on the admin laptop trusts a root that the Java runtime, container image or appliance does not, so the test passes and production fails.
- Turning verification off to make it work: code that sets "trust all" or skips hostname checks to clear an exception removes the protection against interception entirely.
- Forgotten bundled OpenSSL: the OS package is patched while an application ships its own older library, so vulnerability scans of the OS miss it.
Practitioner checklist
- Run
openssl version -aon every build image and record the release line. - Always pass
-servernameand-verify_hostnamewhen testing a named service. - Use
-verify_return_errorin every script or health check. - Test with the same trust store as the failing client (
-CAfile, Java cacerts, OS store). - Serve leaf plus intermediates on every TLS endpoint and verify with a minimal trust store.
- Probe expiry with
x509 -checkendat 30 days and alert on failures. - Keep private keys out of shell history, tickets and chat; use
-nokeyswhere possible. - Plan migration off OpenSSL 1.1.1 and 3.0 to a supported line such as 3.5 LTS.
- Subscribe to OpenSSL security advisories and map them to SBOM entries.
Standards and references
- OpenSSL: openssl-s_client manual
- OpenSSL: openssl-x509 manual
- OpenSSL: openssl-verification-options manual
- OpenSSL: X509_STORE_CTX_get_error verification error codes
- OpenSSL: Release Strategy
- OpenSSL: Vulnerabilities
- Oracle: Java SE SSLPeerUnverifiedException
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: How TLS Works Guide · Certificate and Key File Formats · mTLS Guide · Certificate Revocation Guide