## Intro

This practical guide shows how to inspect, validate, troubleshoot, and safely convert TLS certificates with OpenSSL 3.x. It distinguishes four different checks that are often confused:

- Parsing a certificate file (structure and fields)

- Validating a certificate chain (trust anchor, intermediates, and purpose)

- Verifying a hostname (SAN-based identity check)

- Completing a TCP/TLS connection (handshake success)

You will find copyable commands, what each one proves, what it does not prove, and safety notes to avoid losing keys or exposing secrets.

## 1) Identify your OpenSSL and environment

Options and defaults vary by build and version. OpenSSL 1.1.1 is end-of-life; prefer OpenSSL 3.x. Record your environment before running diagnostics:

openssl version -a 
 Expected evidence: OpenSSL version, build flags, OPENSSLDIR, providers, and default trust locations. These affect verification behavior and available options like -verify_hostname.

Table: Version and environment inventory

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">What to record</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Example evidence</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Why it matters</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OpenSSL version</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OpenSSL 3.0.13</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Determines command options and verification behavior</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Build config</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Options with FIPS provider</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Explains provider availability and algorithms allowed</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OPENSSLDIR</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OPENSSLDIR: \/etc\/ssl</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Location for default CAfile and CApath</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Default trust</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CAfile and CApath values</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Affects s_client and verify trust decisions</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OS and shell</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Linux x86_64, bash</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Reproducibility of commands and paths</td></tr></tbody>
</table>
</div>

## 2) Inspect a local certificate (PEM)

Parse a PEM certificate and focus on identity and validity periods. Modern hostname identity uses SAN, not Common Name alone.

openssl x509 -in <certificate.pem> -noout \
 -subject -issuer -serial -dates \
 -fingerprint -sha256 \
 -ext subjectAltName 

- subject: Descriptive identity fields

- issuer: Who signed it

- serial: Unique per issuer

- dates: notBefore and notAfter validity window

- fingerprint with -sha256: SHA-256 fingerprint for tracking

- subjectAltName: List of DNS names and IPs the certificate is valid for (primary source for hostname checks)

 Verification step: confirm that your target hostname is present in DNS entries within SAN.

## 3) Check expiration windows without guessing renewal policy

Test whether a certificate will remain valid for at least N seconds. Example: 30 days is 2592000 seconds.

openssl x509 -checkend 2592000 -noout -in <certificate.pem>
echo $? 

- Exit status 0: will NOT expire within 30 days

- Non-zero: expires within 30 days or an error occurred

 Verification step: if non-zero, confirm the actual notAfter date with the command in section 2.

## 4) Inspect a remote endpoint with s_client (handshake and presented chain)

Use Server Name Indication (SNI) to select the correct virtual host. Ask for the full chain as served, and request a nonzero exit code on verification errors. The input redirection closes stdin so the command exits cleanly.

openssl s_client -connect <host>:443 -servername <host> \
 -showcerts -verify_return_error </dev/null 
 What to look for:

- Certificate chain as presented by the server (may be incomplete)

- Handshake completion vs. verification status

- Whether the leaf certificate matches the expected hostname (not checked automatically without -verify_hostname)

Notes:

- Trust-store selection depends on your OpenSSL build and OS defaults. You can override with -CAfile or -CApath when reproducing issues.

- A successful handshake or a displayed certificate does not, by itself, prove identity or trust.

## 5) Explicit hostname verification (OpenSSL 3.x)

First, confirm your s_client supports -verify_hostname.

openssl s_client -help 2>&1 | grep -i verify_hostname || echo "Option not supported" 
 If supported (OpenSSL 3.x recommended):

openssl s_client -connect <host>:443 -servername <host> \
 -verify_hostname <host> -verify_return_error </dev/null 
 Expected evidence: verification OK when SAN includes the hostname and the chain validates to a trusted anchor. If verification fails, s_client returns a nonzero status and prints the failing reason.

## 6) Validate a local chain (trust anchor vs. intermediates)

Separate the trust anchor (root CA) from untrusted intermediates and validate the leaf.

openssl verify -CAfile <root-ca.pem> -untrusted <intermediate.pem> <leaf.pem> 
 Expected evidence: <leaf.pem>: OK. If it fails, reasons commonly include expired intermediates, wrong order, missing intermediate, or an untrusted root.

Important distinctions:

- Chain validation does not check hostnames; it checks signatures, time, and trust to an anchor.

- Purpose checks (e.g., sslserver vs sslclient) are separate; use -purpose sslserver if you need that explicit verification.

## 7) Inspect a CSR safely

openssl req -in <request.csr> -noout -text -verify 

- The -verify flag confirms the CSR is self-signed by the embedded public key, proving possession of the corresponding private key when the CSR was created.

- It does not prove the requester’s identity or that any CA will sign it.

 Verification step: confirm requested SAN values and key usage extensions match your intended issuance policy.

## 8) Recognize and convert certificate formats without overwriting

Always create a new file and verify before replacing the original.

- Recognize a certificate: openssl x509 -in <file> -noout -subject

- Recognize a private key without exposing it: openssl pkey -in <key.pem> -noout

Table: Certificate format and conversion matrix (non-destructive)

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">From</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">To</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Command</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Verification step</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PEM certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">DER certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.pem&gt; -outform DER -out &lt;certificate.der&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.der&gt; -inform DER -noout -subject</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">DER certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PEM certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.der&gt; -inform DER -out &lt;certificate.pem.new&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.pem.new&gt; -noout -subject</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PKCS#12 bundle</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PEM leaf cert</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl pkcs12 -in &lt;bundle.p12&gt; -clcerts -nokeys -out &lt;leaf-cert.pem&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;leaf-cert.pem&gt; -noout -subject</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PKCS#12 bundle</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Encrypted PEM key</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl pkcs12 -in &lt;bundle.p12&gt; -nocerts -out &lt;key.pem&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl pkey -in &lt;key.pem&gt; -noout</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PEM cert and key</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">PKCS#12 bundle</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl pkcs12 -export -inkey &lt;key.pem&gt; -in &lt;certificate.pem&gt; -out &lt;bundle.p12&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl pkcs12 -in &lt;bundle.p12&gt; -info -nokeys</td></tr></tbody>
</table>
</div>

## 9) PKCS#12 inspection and export, securely

Avoid putting passwords directly on the command line; they can leak via shell history or process listings. Prefer interactive prompts, or use protected mechanisms provided by your environment.

- Inspect certificates inside a PKCS#12 without printing private keys:

openssl pkcs12 -in <bundle.p12> -info -nokeys 

- Export only the leaf certificate (no keys):

 openssl pkcs12 -in <bundle.p12> -clcerts -nokeys -out <leaf-cert.pem> 

- Export an encrypted private key (you will be prompted for an output passphrase):

 openssl pkcs12 -in <bundle.p12> -nocerts -out <key.pem> 
 Verification step: parse outputs with openssl x509 or openssl pkey as shown, and confirm file permissions restrict access to the private key.

## 10) Match a certificate to its private key without exposing the key

Derive public-key fingerprints from both sides and compare. They must be identical.

- From the certificate:

openssl x509 -in <certificate.pem> -noout -pubkey | openssl sha256 

- From the private key:

 openssl pkey -in <key.pem> -pubout | openssl sha256 
 Expected evidence: the two SHA-256 digests match. Caveats:

- The older -modulus method only applies to RSA, not ECDSA or Ed25519.

- Do not display private-key material; extracting a public key from it is sufficient for matching.

## 11) What each command proves (and doesn’t)

Table: Common inspection commands and their limits

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Command</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Proves</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Does not prove</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.pem&gt; -noout -text</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Certificate fields, SAN, validity window</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Trust to a root, hostname ownership, live connectivity</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -checkend N -in &lt;certificate.pem&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Time until expiration crosses N</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Whether a CA will reissue, application reload behavior</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect -servername -showcerts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">TLS handshake succeeds, presented chain</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hostname validation unless -verify_hostname is used, app-specific trust policy</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -verify_hostname &lt;host&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hostname identity check with SAN and chain</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">That your OS or app uses the same trust store or cipher policy</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl verify -CAfile -untrusted &lt;leaf.pem&gt;</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Signature chain to trust anchor, time, purpose if specified</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hostname match, live server configuration</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl req -in &lt;request.csr&gt; -verify</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CSR integrity and key possession at creation</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Requester identity or CA approval</td></tr></tbody>
</table>
</div>

## 12) Troubleshoot common TLS failures

Start with read-only inspection. Do not modify files until you know the cause.

Table: TLS failure symptom, likely cause, first safe diagnostic

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Symptom</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Likely cause</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">First safe diagnostic</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Handshake OK, browser shows name mismatch</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Wrong SAN or wrong virtual host</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect &lt;host&gt;:443 -servername &lt;host&gt; -verify_hostname &lt;host&gt; &lt;/dev/null</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Handshake fails with certificate expired</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Expired or not yet valid certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.pem&gt; -noout -dates and -checkend N</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Unknown CA or self-signed error</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Untrusted root or missing trust anchor</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl verify -CAfile &lt;root-ca.pem&gt; &lt;leaf.pem&gt;</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Works on some clients, not others</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Missing or wrong intermediate on server</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect &lt;host&gt;:443 -showcerts &lt;/dev/null</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Correct cert, still wrong site</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Missing -servername SNI in client or server misrouting</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect &lt;host&gt;:443 -servername &lt;host&gt; &lt;/dev/null</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Sporadic failures by time of day</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">System clock skew</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">date on client and server; check notBefore and notAfter</td></tr></tbody>
</table>
</div>

## 13) Safe operational practices

- Start with read-only parsing and verification; preserve originals and permissions.

- Never replace a working certificate or key without creating a new file and verifying it first.

- Restrict key access with filesystem permissions; audit who can read private keys.

- Do not paste private keys or full PKCS#12 contents into tickets or logs.

- Confirm application reload behavior separately: some servers require a restart to use a new certificate or key.

- Document the trust anchor and intermediate chain explicitly in deployment automation.

- Remember that openssl s_client is a diagnostic tool; it does not exactly reproduce every application’s TLS stack, trust store, or policy.

## 14) Pre-deployment and post-deployment checklist

Table: Deployment checks you can verify quickly

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Phase</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Checklist item</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Evidence</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Before deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OpenSSL 3.x available and recorded</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl version -a captured in logs</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Before deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Leaf cert SAN includes the target hostnames</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl x509 -in &lt;certificate.pem&gt; -noout -ext subjectAltName</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Before deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Chain validates to intended trust anchor</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl verify -CAfile &lt;root-ca.pem&gt; -untrusted &lt;intermediate.pem&gt; &lt;leaf.pem&gt;</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Before deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Key matches the certificate</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Matching SHA-256 of derived public keys</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">After deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Server presents complete chain</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect &lt;host&gt;:443 -servername &lt;host&gt; -showcerts &lt;/dev/null</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">After deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hostname verification succeeds</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">openssl s_client -connect &lt;host&gt;:443 -servername &lt;host&gt; -verify_hostname &lt;host&gt; &lt;/dev/null</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">After deploy</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">App reload or restart confirmed</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">App logs and versioned config audit</td></tr></tbody>
</table>
</div>

## Conclusion

When diagnosing TLS, treat parsing, chain validation, hostname verification, and the network handshake as distinct steps. Start with version inventory, inspect local files safely, then test the live endpoint with SNI and explicit hostname checks. Keep conversions non-destructive, never expose private keys, and verify application reload behavior independently. With these OpenSSL 3.x commands and checklists, you can move from symptoms to evidence-backed fixes quickly and safely.