Report this

What is the reason for this report?

OpenSSL Essentials: SSL Certificates, Private Keys and CSRs

Updated on May 3, 2026
OpenSSL Essentials: SSL Certificates, Private Keys and CSRs

Introduction

OpenSSL is the command-line toolkit most administrators use to create keys, CSRs, and X.509 certificates, inspect PEM files, and convert formats for servers and clients. This tutorial collects practical openssl commands for RSA and elliptic-curve keys, CSRs with Subject Alternative Names (SANs), self-signed certificates, verification, live TLS checks with s_client, and PEM, DER, PKCS12, and PKCS7 workflows. It is written for operators who manage HTTPS on Linux servers, submit CSRs to public or private CAs, or troubleshoot handshake and trust problems.

How to Use This Guide:

  • If you need background on CSRs, DN fields, and non-interactive -subj, start under What Is OpenSSL and Why It Matters for SSL/TLS Management and Creating Certificate Signing Requests (CSRs).
  • Use Generating Private Keys with OpenSSL when you need new RSA or EC material, password protection, or a concise comparison of RSA 2048, RSA 4096, and P-256 curves.
  • Jump to Creating Self-Signed Certificates for lab or internal TLS where browser trust is not required.
  • Use Inspecting and Decoding Certificates, CSRs, and Keys, Verifying Certificate Validity and Expiry, and Testing Live TLS Connections with openssl s_client when validating files or remote services.
  • Use Converting Certificate Formats when Windows, Java, or legacy apps require DER, PFX, or P7B bundles.
  • Most commands use line continuation (\) for readability; you can paste them as multi-line blocks or join them into one line.

Key Takeaways

  • Generate RSA keys with genrsa or genpkey, and EC keys with ecparam, including optional password encryption with AES-256.
  • Build CSRs with -newkey, from an existing key, from an existing certificate, or from an OpenSSL config that carries SANs for DNS names and IP addresses.
  • Issue self-signed certificates for non-production use, including SANs via -addext on OpenSSL 1.1.1 and newer.
  • Inspect PEM-encoded certificates and CSRs, print RSA key structure with openssl rsa -text, and confirm that a key matches a cert or CSR using modulus hashes (RSA) or public key comparison (EC).
  • Read notBefore and notAfter, verify a leaf against a CA file, and validate a chain with -untrusted intermediates per CA/Browser Forum Baseline Requirements practice.
  • Debug live TLS with openssl s_client, including SNI, TLS 1.2 and TLS 1.3 probes (RFC 8446), and stapled OCSP status lines.
  • Convert between PEM, DER, PKCS12 (PFX), and PKCS7 (P7B), including PKCS12 -legacy compatibility when older clients cannot read modern defaults.

What Is OpenSSL and Why It Matters for SSL/TLS Management

OpenSSL ships by default on most Linux distributions and provides the openssl command for every file-based step in TLS certificate work: generating keys, building CSRs, signing certificates, inspecting PEM files, and converting between formats. Apache, NGINX, HAProxy, Postfix, and most Python and Node.js TLS libraries link against it directly, which means the same tool you use to issue a CSR also generates the keys those services consume in production.

OpenSSL’s Role in Certificate Workflows

Every TLS certificate moves through five stages: key generation, CSR creation, signing (by a public CA, internal CA, or self-signed), deployment to the consuming service, and renewal before expiry. OpenSSL provides a dedicated subcommand for each stage. The certificate format produced at the signing stage follows the X.509 profile defined in RFC 5280.

Private Key Generation
        |
        v
   CSR Creation
        |
        v
  CA Signing (or Self-Sign)
        |
        v
Certificate Deployment
        |
        v
  Renewal / Revocation

Key Concepts: Private Keys, CSRs, and Certificates Explained

Private key: A secret PEM or DER file that must stay on servers or HSMs you control. It signs proofs during TLS handshakes and must never be emailed, pasted into tickets, or checked into version control.

CSR (certificate signing request): A PEM-encoded PKCS#10 request that bundles your public key with a Distinguished Name (DN) and optional extensions (for example SANs). You send the CSR to a CA; the CA returns an identity-bound certificate without needing your private key.

Certificate: An X.509 public-key certificate (RFC 5280) that binds a subject name (and SAN entries) to a public key, carries validity dates, and may chain to a trusted root. Browsers and TLS clients use that binding during server authentication.

A Brief History of OpenSSL

OpenSSL traces its lineage to SSLeay, Eric Young’s SSL library from the 1990s, which formed the basis of the OpenSSL project’s first releases. The Heartbleed vulnerability disclosed in 2014 affected OpenSSL 1.0.1’s TLS heartbeat extension and prompted widespread coordination on patching, key rotation, and funding for maintenance. The OpenSSL Software Foundation now stewards the codebase. OpenSSL 3.0 released in September 2021 with a five-year LTS support window, followed by 3.1 (March 2023), 3.2 (November 2023), 3.3 (April 2024), and 3.4 (October 2024) on roughly annual cadences. OpenSSL 1.1.1 reached end of life in September 2023; production hosts should run 3.0 LTS or a newer maintained branch.

Generating Private Keys with OpenSSL

Private keys are the root of trust for your TLS identities. Generate them on trusted hosts, restrict file permissions (for example chmod 600 on key files), and store pass phrases outside configuration repositories when keys are encrypted.

Generate an RSA Private Key

This command writes an unencrypted 2048-bit RSA private key to a PEM file:

# genrsa: RSA key generation; -out sets the PEM path; 2048 selects the modulus size in bits
openssl genrsa -out domain.key 2048

Example output:

Generating RSA private key, 2048 bit long modulus
........................................................................+++++
............+++++
writing new private key to 'domain.key'
Flag or argument Purpose
genrsa OpenSSL subcommand to generate an RSA private key
-out domain.key Output path for the PEM-encoded key
2048 RSA modulus size in bits

OpenSSL 3.x continues to support genrsa, but the manual prefers openssl-genpkey for new scripts because it unifies algorithm selection. Equivalent RSA generation:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out domain.key
Flag Purpose
-algorithm RSA Generate an RSA key pair
-pkeyopt rsa_keygen_bits:2048 Set the RSA modulus size
-out domain.key PEM output path

Generate an EC (Elliptic Curve) Private Key

This command generates a P-256 (prime256v1) EC private key in PEM format:

openssl ecparam -genkey -name prime256v1 -noout -out ec.key
Flag Purpose
ecparam EC parameter and key generation utility
-genkey Generate a private key from the named curve
-name prime256v1 Use the NIST P-256 curve (widely supported for TLS)
-noout Suppress printing EC parameters to stdout
-out ec.key Write the PEM key to disk

A P-256 EC key offers security comparable to RSA 3072 at a fraction of the byte size (256 bits vs 3072 bits). On modern x86 hardware, P-256 signing is roughly an order of magnitude faster than RSA 2048 signing, which reduces server CPU on high-handshake-rate workloads. Verification is the inverse (RSA verifies faster than P-256), but TLS servers sign once per handshake while clients verify, so the asymmetry favors EC at the server.

Generate a Password-Protected Private Key

This command creates a new 2048-bit RSA key encrypted with AES-256:

openssl genrsa -aes256 -out domain.key 2048
Flag Purpose
genrsa Generate an RSA private key
-aes256 Encrypt the PEM key with AES-256 before writing
-out domain.key Output path
2048 RSA modulus size in bits

You enter a pass phrase when prompted. Older documentation often shows -des3; Triple-DES remains available for compatibility, but AES-256 is the better default for new keys.

This legacy invocation matches older tutorials and still works when you must match Triple-DES-encrypted PEM expectations:

openssl genrsa -des3 -out domain.key 2048
Flag Purpose
-des3 Encrypt the new key with Triple-DES
-out PEM output path
2048 RSA modulus size

Encrypt an existing unencrypted key (same operation class as protecting material at rest):

openssl rsa -aes256 \
       -in unencrypted.key \
       -out encrypted.key
Flag Purpose
rsa RSA key processing command
-aes256 Encrypt output key with AES-256
-in unencrypted.key Existing PEM private key
-out encrypted.key Encrypted PEM output

Triple-DES encryption for an existing RSA key matches the older openssl rsa -des3 pattern:

openssl rsa -des3 \
       -in unencrypted.key \
       -out encrypted.key
Flag Purpose
-des3 Encrypt with Triple-DES
-in Source PEM key
-out Encrypted PEM destination

Decrypt an encrypted key (requires the pass phrase):

openssl rsa \
       -in encrypted.key \
       -out decrypted.key
Flag Purpose
rsa RSA key processing command
-in encrypted.key Encrypted PEM input
-out decrypted.key Decrypted PEM output

RSA 2048 vs 4096 vs EC 256: When to Use Each

Key Type Approximate Security Level Signing (relative to RSA 2048) Verification (relative to RSA 2048) Recommended Use Case
RSA 2048 ~112 bits 1x (baseline) 1x (baseline) Default for public HTTPS; broad client compatibility
RSA 4096 ~140 bits ~5x to 7x slower ~3x slower Internal CAs, regulated environments mandating longer keys, root or intermediate CAs
EC P-256 (prime256v1) ~128 bits ~10x to 20x faster ~3x to 5x slower Modern web servers, CDNs, mTLS deployments with EC support across the chain

For real numbers on your hardware, run openssl speed rsa2048 rsa4096 ecdsap256. The relative ratios above hold on most x86_64 servers; absolute throughput depends on CPU, AES-NI availability, and cipher suite.

Creating Certificate Signing Requests (CSRs)

CSRs encode the subject, public key, and requested extensions so a CA can issue a certificate that matches your DNS names and identity checks.

About Certificate Signing Requests

If you obtain an SSL certificate from a public or private CA, you generate a CSR that contains your public key plus signing inputs the CA needs. Both components are incorporated into the issued certificate.

When you create a CSR interactively, OpenSSL prompts for a Distinguished Name (DN). The Common Name (CN) must match the primary hostname when SANs are absent; modern certificates should include SANs for every hostname clients will use.

Other DN fields identify your organization. Commercial CAs often require accurate legal names and locations.

Interactive prompts resemble:

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:New York
Locality Name (eg, city) []:Brooklyn
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example Brooklyn Company
Organizational Unit Name (eg, section) []:Technology Division
Common Name (e.g. server FQDN or YOUR name) []:examplebrooklyn.com
Email Address []:

To pass subject fields non-interactively, append the -subj option to any openssl req command:

openssl req \
    -newkey rsa:2048 -nodes -keyout domain.key \
    -out domain.csr \
    -subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=examplebrooklyn.com"

See openssl-req for additional DN controls.

Generate a CSR and Private Key in One Command

Use this method when you need a new RSA key and CSR together for CA submission:

openssl req \
       -newkey rsa:2048 -nodes -keyout domain.key \
       -out domain.csr \
       -sha256

Complete the CSR prompts unless you also pass -subj.

Flag Purpose
req CSR and certificate request handling (openssl-req)
-newkey rsa:2048 Create a new 2048-bit RSA key pair
-nodes Do not encrypt the private key (equivalent to “no DES”)
-keyout domain.key Private key output path
-out domain.csr CSR output path
-sha256 Sign the CSR with SHA-256

Generate a CSR Using an Existing Private Key

Use this method when the key already exists and only the CSR is new:

openssl req \
       -key domain.key \
       -new -out domain.csr
Flag Purpose
req CSR handling command
-key domain.key Existing private key
-new Generate a new CSR
-out domain.csr CSR output path

Generate a CSR from an Existing Certificate and Private Key

Use this method when you renew or reissue and need a CSR that mirrors an existing certificate’s subject because the original CSR is missing:

openssl x509 \
       -in domain.crt \
       -signkey domain.key \
       -x509toreq -out domain.csr
Flag Purpose
x509 X.509 certificate utilities (openssl-x509)
-in domain.crt Source certificate PEM
-signkey domain.key Private key matching the certificate
-x509toreq Convert an X.509 certificate into a CSR
-out domain.csr CSR output path

Generate a CSR with a Config File (Including SANs)

This workflow keeps SANs out of interactive prompts and documents requested DNS names and addresses in one file.

1. Create san.cnf:

[req]
default_bits       = 2048
prompt             = no
default_md         = sha256
distinguished_name = dn
req_extensions     = req_ext

[dn]
C  = US
ST = New York
L  = Brooklyn
O  = Example Brooklyn Company
CN = examplebrooklyn.com

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = examplebrooklyn.com
DNS.2 = www.examplebrooklyn.com
IP.1  = 192.168.1.10

2. Generate the CSR:

openssl req \
    -new \
    -key domain.key \
    -out domain.csr \
    -config san.cnf
Flag Purpose
-new New CSR
-key domain.key Existing private key
-out domain.csr CSR output
-config san.cnf Request extensions and subject from config

Common errors with san.cnf:

  • Error opening config file san.cnf: relative path issue. Pass an absolute path or run openssl from the directory containing the file.
  • SANs absent from the resulting CSR: confirm req_extensions = req_ext is set under [req] and that [req_ext] references [alt_names]. OpenSSL produces a CSR without SANs if a section name is misspelled, with no warning.
  • error in req on generation: every DN field in [dn] must sit on its own line with = separators. Trailing whitespace or BOM bytes in a saved file cause parse failures.

3. Confirm SANs:

openssl req -text -noout -in domain.csr | grep -A1 "Subject Alternative"
Flag or fragment Purpose
-text Decode CSR fields for reading
-noout Omit PEM blob
-in CSR input path
grep -A1 Print SAN lines plus one context line

SANs do not appear when you only pass -subj on the command line. Use a config file (shown above) or -addext on OpenSSL 1.1.1 or newer to embed SANs in the CSR.

Single-command CSR with -addext (OpenSSL 1.1.1+):

openssl req \
    -newkey rsa:2048 -nodes -keyout domain.key \
    -out domain.csr \
    -subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=examplebrooklyn.com" \
    -addext "subjectAltName=DNS:examplebrooklyn.com,DNS:www.examplebrooklyn.com"
Flag Purpose
-newkey rsa:2048 Generate new RSA key
-nodes Leave key unencrypted
-keyout / -out Key and CSR paths
-subj Inline DN
-addext Attach SAN extension expression

Creating Self-Signed Certificates

Self-signed certificates encrypt TLS channels but are not anchored in public trust stores. Use them for development, administrative interfaces, or private networks where you distribute your own roots.

Generate a Self-Signed Certificate from a New Key

This command creates a new RSA key and a self-signed certificate:

openssl req \
       -newkey rsa:2048 -nodes -keyout domain.key \
       -x509 -days 365 -out domain.crt
Flag Purpose
-newkey rsa:2048 Generate new RSA key
-nodes Do not encrypt key
-keyout Private key path
-x509 Emit a certificate instead of only a CSR
-days 365 Validity window
-out Certificate path

Generate a Self-Signed Certificate from an Existing Private Key

This command generates a self-signed certificate from a private key you already have, prompting for subject details unless you add -subj:

openssl req \
       -key domain.key \
       -new \
       -x509 -days 365 -out domain.crt
Flag Purpose
-key Existing private key
-new Build signing inputs interactively or via config
-x509 Output self-signed certificate
-days 365 Validity
-out Certificate path

Generate a Self-Signed Certificate from an Existing Private Key and CSR

openssl x509 \
       -signkey domain.key \
       -in domain.csr \
       -req -days 365 -out domain.crt
Flag Purpose
-signkey Private key used to self-sign
-in CSR input
-req Treat input as a CSR
-days Validity
-out Certificate output

Generate a Self-Signed Certificate with SANs

Modern TLS clients expect SAN entries even when the certificate is self-signed:

openssl req \
    -newkey rsa:2048 -nodes -keyout domain.key \
    -x509 -days 365 -out domain.crt \
    -subj "/CN=examplebrooklyn.com" \
    -addext "subjectAltName=DNS:examplebrooklyn.com,DNS:www.examplebrooklyn.com"
Flag Purpose
-addext Attach SAN list (requires OpenSSL 1.1.1+)
Other flags Same roles as the self-signed examples above

Current browsers treat the SAN extension as authoritative for hostnames. Self-signed certificates without SANs often fail name checks even after you trust the certificate manually.

Deploy the Certificate to a Web Server

After generating a key and certificate, place them where your web server can read them, set permissions correctly, and reload the service.

Standard file layout on Linux:

sudo mv domain.crt /etc/ssl/certs/domain.crt
sudo mv domain.key /etc/ssl/private/domain.key
sudo chmod 644 /etc/ssl/certs/domain.crt
sudo chmod 600 /etc/ssl/private/domain.key
sudo chown root:root /etc/ssl/certs/domain.crt /etc/ssl/private/domain.key

Reload NGINX after pointing the config at the new files:

sudo nginx -t && sudo systemctl reload nginx

Reload Apache:

sudo apachectl configtest && sudo systemctl reload apache2

Always run the configuration test before reloading. A typo in NGINX or Apache TLS directives can prevent the service from starting after a reload, dropping all live connections. The nginx -t and apachectl configtest checks parse the config without applying it.

Inspecting and Decoding Certificates, CSRs, and Keys

Use these commands to read the content of a certificate, CSR, or private key without converting the file first.

View the Contents of a Certificate

This command prints the decoded X.509 fields for inspection per RFC 5280 profile:

openssl x509 -text -noout -in domain.crt
Flag Purpose
x509 Certificate tool (openssl-x509)
-text Human-readable output
-noout Omit PEM encoding on stdout
-in Certificate path

Truncated example:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ...
        Issuer: CN = Example CA
        Validity
            Not Before: Jan  1 00:00:00 2026 GMT
            Not After : Dec 31 23:59:59 2027 GMT
        Subject: CN = examplebrooklyn.com
        ...
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:examplebrooklyn.com, DNS:www.examplebrooklyn.com

Decode and View CSR Details

openssl req -text -noout -verify -in domain.csr
Flag Purpose
req CSR utilities
-text Human-readable CSR
-noout Omit PEM dump
-verify Verify CSR signature
-in CSR path

Example snippet:

Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = US, ST = New York, L = Brooklyn, O = Example Brooklyn Company, CN = examplebrooklyn.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name:
                DNS:examplebrooklyn.com, DNS:www.examplebrooklyn.com

View a Private Key’s Details

For RSA keys:

openssl rsa -text -noout -in domain.key
Flag Purpose
rsa RSA key parser
-text Print modulus, exponents, and primes
-noout Omit PEM encoding
-in Key path

For EC keys, swap rsa for ec:

openssl ec -text -noout -in ec.key
Flag Purpose
ec EC private-key utility
-text Print curve parameters and key structure
-noout Omit PEM encoding on stdout
-in ec.key PEM key input

Validate an RSA key file without printing full structure:

openssl rsa -check -in domain.key
Flag Purpose
rsa RSA private-key utility
-check Test exponent/consistency without printing the full key
-in domain.key PEM key input

Encrypted keys prompt for a pass phrase; success confirms the PEM parses.

Never paste private key material, decrypted output, or rsa -text dumps into public tickets, chat logs, or monitoring systems.

Verify That a Private Key Matches a Certificate and CSR

These commands compare RSA modulus digests so you can confirm one key belongs to one certificate and CSR:

openssl rsa -noout -modulus -in domain.key | openssl md5
openssl x509 -noout -modulus -in domain.crt | openssl md5
openssl req -noout -modulus -in domain.csr | openssl md5
Pipeline segment Purpose
-noout -modulus Print modulus for comparison
openssl md5 Short fingerprint for manual comparison

Matching MD5 lines imply the same RSA key pair underlies each artifact.

Common error: modulus mismatch. If the MD5 hashes do not match, the files belong to different key pairs. The most frequent causes are generating a new key after creating the CSR and then trying to use the original CSR with the new key, or installing a CA-signed certificate against the wrong key on the server. Re-issue the CSR from the correct key, or replace the deployed key with the one used to generate the CSR.

For EC keys, compare exported public keys instead of RSA modulus lines:

openssl ec -pubout -in ec.key | openssl md5
openssl x509 -pubkey -noout -in domain.crt | openssl md5

Identical MD5 lines confirm the EC key matches the certificate.

Verifying Certificate Validity and Expiry

Run these commands to confirm a certificate is within its validity window and that it chains to a CA you trust. The openssl verify command checks the chain against the file or directory you pass via -CAfile or -CApath, not against the system trust store used by browsers.

Check a Certificate’s Expiry Date

openssl x509 -enddate -noout -in domain.crt

Example:

notAfter=Dec 31 23:59:59 2027 GMT

Both endpoints:

openssl x509 -dates -noout -in domain.crt
Flag Purpose
-enddate Print notAfter
-dates Print notBefore and notAfter
-noout Suppress PEM
-in Certificate input

Verify a Certificate Against a CA Bundle

openssl verify -verbose -CAfile ca.crt domain.crt
Flag Purpose
verify Chain verification against trusted anchors
-verbose Explain verification steps
-CAfile PEM file containing trusted CA certificates
Final argument Leaf certificate to verify

Exit code 0 means OpenSSL built a path to a trusted anchor consistent with RFC 5280 processing rules. Failure example:

CN = wrong.example.com
error 62 at 0 depth lookup: Hostname mismatch
domain.crt: verification failed

Verify a Full Certificate Chain

openssl verify -CAfile ca-chain.pem -untrusted intermediate.pem domain.crt
Flag Purpose
-CAfile Trusted root or CA bundle you control
-untrusted Intermediate certificates that are not trusted roots but may form the path
Final argument Server or leaf certificate

Place intermediates in -untrusted when verifying how browsers would chain signed TLS certificates. Public CAs must issue subscriber certificates that conform to CA/Browser Forum Baseline Requirements, including correct chaining practices.

Common errors during chain verification:

  • unable to get local issuer certificate: the verifier cannot find the intermediate that signed your leaf. Pass it via -untrusted intermediate.pem or include it in your CA bundle.
  • self signed certificate in certificate chain: a root in your chain is being treated as untrusted. Either add it to -CAfile, or remove the redundant self-signed cert from the chain you ship to clients.
  • certificate has expired: read notAfter with openssl x509 -enddate -noout -in cert.pem. If the leaf is current, check each intermediate the same way.

Testing Live TLS Connections with openssl s_client

Use openssl-s_client to reproduce client-side handshakes without a browser.

Connect to a Remote Server and Inspect Its Certificate

openssl s_client -connect example.com:443 -servername example.com
Flag Purpose
-connect host:port TCP endpoint
-servername Send SNI, required when the host serves multiple certificates

Truncated output excerpt:

CONNECTED(00000003)
---
Certificate chain
 0 s:CN = example.com
   i:C = US, O = Example CA, CN = Example Intermediate
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=CN = example.com
issuer=C = US, O = Example CA, CN = Example Intermediate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

Leave the session with Ctrl+C or press Q when OpenSSL is waiting for stdin after the handshake summary.

Test Specific TLS Protocol Versions

# Test TLS 1.2
openssl s_client -connect example.com:443 -tls1_2

# Test TLS 1.3
openssl s_client -connect example.com:443 -tls1_3
Flag Purpose
-connect host:port TCP endpoint for the handshake
-tls1_2 Restrict the client hello to TLS 1.2
-tls1_3 Restrict the client hello to TLS 1.3 (RFC 8446)

If the server lacks support for the selected version, the handshake fails quickly. Treat that as a capability signal, not necessarily a production defect.

Check OCSP Stapling and Certificate Revocation

openssl s_client -connect example.com:443 -status 2>/dev/null | grep -A 17 'OCSP response:'
Flag or fragment Purpose
-connect Remote host and port
-status Ask the server to include a stapled OCSP response
2>/dev/null Hide stderr so grep filters cleaner console output
grep -A 17 Show the OCSP stanza plus following lines

OCSP stapling lets the server attach a fresh revocation proof during the TLS handshake. If you see OCSP response: no response sent, the server chose not to staple, which is common and not automatically an error.

Add -showcerts to dump each certificate in the peer chain for offline inspection or saving to PEM files.

Diagnose Common TLS Errors with s_client

Three handshake failures account for most production TLS issues. Each one shows up in distinct s_client output.

Hostname mismatch. The certificate’s SAN list does not include the hostname you connected to:

verify error:num=62:Hostname mismatch

Re-issue the certificate with the correct SAN entries, or connect with the hostname that is actually in the SAN list.

Expired certificate or chain element:

verify return code: 10 (certificate has expired)

Inspect every certificate the server presents, including intermediates. The pipeline below splits the chain into individual files and prints the subject and expiry date for each:

openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERTIFICATE/{n++} n{print > ("/tmp/cert-" n ".pem")}' \
  && for f in /tmp/cert-*.pem; do openssl x509 -subject -enddate -noout -in "$f"; done \
  && rm /tmp/cert-*.pem

The awk step writes each PEM block to a numbered file under /tmp/, the for loop prints subject and notAfter per certificate, and the final rm cleans up the temporary files. This pipeline uses only POSIX awk and runs on Linux, macOS, and BSD.

Untrusted intermediate (server sent leaf only):

verify return code: 21 (unable to verify the first certificate)

The server is not bundling the intermediate. For NGINX, concatenate the leaf and the intermediate into one file and point ssl_certificate at it:

cat domain.crt intermediate.crt > fullchain.crt

Test mutual TLS (client certificate authentication):

openssl s_client \
    -connect example.com:443 \
    -cert client.crt \
    -key client.key

Use this when the server requires a client certificate. A server that requires mTLS but receives no client cert closes the handshake with tlsv13 alert certificate required.

Converting Certificate Formats

Format Overview and Conversion Paths

From Format To Format Common Use Case Command Summary
PEM DER Java keystores, Windows imports openssl x509 -outform der
DER PEM Editing with text tools openssl x509 -inform der
PEM PKCS12 Windows IIS, bundled client certs openssl pkcs12 -export
PKCS12 PEM Splitting key and certs openssl pkcs12 -nodes
PEM PKCS7 P7B bundles for some Microsoft flows openssl crl2pkcs7
PKCS7 PEM Inspecting P7B files openssl pkcs7 -print_certs

Convert PEM to DER

openssl x509 \
       -in domain.crt \
       -outform der -out domain.der
Flag Purpose
x509 Certificate conversion
-in PEM input
-outform der Binary DER output
-out Destination file

Convert DER to PEM

This command converts a binary DER-encoded certificate back to a PEM-encoded file:

openssl x509 \
       -inform der -in domain.der \
       -out domain.crt
Flag Purpose
-inform der Interpret input as DER
-in DER path
-out PEM output

Convert PEM to PKCS12 (PFX)

Full export with CA chain:

openssl pkcs12 \
    -export \
    -inkey domain.key \
    -in domain.crt \
    -certfile ca-chain.pem \
    -out domain.pfx
Flag Purpose
-export Create a PKCS#12 bag (openssl-pkcs12)
-inkey Private key
-in Leaf certificate
-certfile Additional certificates (typically intermediates)
-out PKCS12 path

Minimal export without separate chain file (still valid when the leaf PEM already includes intermediates concatenated):

openssl pkcs12 \
       -inkey domain.key \
       -in domain.crt \
       -export -out domain.pfx
Flag Purpose
-inkey Private key PEM
-in Leaf certificate PEM
-export Create (rather than parse) a PKCS#12 container
-out PKCS#12 output path

You may leave export passwords blank when tooling permits.

OpenSSL 3 changed default PKCS#12 algorithms. Add -legacy when importing generated PFX files into older Windows components or legacy Java keystores that reject modern KDF settings.

Convert PKCS12 to PEM

openssl pkcs12 \
       -in domain.pfx \
       -nodes -out domain.combined.crt
Flag Purpose
-in PKCS12 source
-nodes Do not encrypt the private key in the output PEM
-out Combined PEM with key and certificates

Convert PEM to PKCS7 (P7B)

openssl crl2pkcs7 -nocrl \
       -certfile domain.crt \
       -certfile ca-chain.crt \
       -out domain.p7b
Flag Purpose
crl2pkcs7 Build PKCS#7 objects
-nocrl Do not embed a CRL
-certfile Repeatable PEM certificate inputs
-out PKCS#7 output

Convert PKCS7 to PEM

openssl pkcs7 \
       -in domain.p7b \
       -print_certs -out domain.crt
Flag Purpose
pkcs7 PKCS#7 utility
-in PKCS#7 input
-print_certs Emit contained certificates as PEM
-out Destination PEM

OpenSSL Version

The openssl version command reports your binary version and build-time options, which affect feature availability.

openssl version -a
Flag Purpose
version Print library version metadata
-a Include build flags, directories, and compiler details

Example OpenSSL 3.x output:

OpenSSL 3.0.13 30 Jan 2024
built on: Wed Apr 10 12:00:00 2024 UTC
platform: linux-x86_64
compiler: gcc ...
OPENSSLDIR: "/usr/lib/ssl"
ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-3"
MODULESDIR: "/usr/lib/x86_64-linux-gnu/ossl-modules"
Seeding source: os-specific

OpenSSL 1.0.x and 1.1.x branches are end of life upstream; production hosts should run maintained OpenSSL 3.x releases supplied by your OS vendor or follow the OpenSSL release strategy if you build from source.

OpenSSL vs Alternatives: LibreSSL, BoringSSL, and Rustls

OpenSSL remains the default toolkit on most Linux servers and the widest documented surface for file-based PKI. Alternative libraries exist because vendors optimize for different constraints: licensing, code review surface, API stability, or memory safety guarantees.

Library Maintained By TLS 1.3 Support Key Strength Best For
OpenSSL OpenSSL team / community Yes Broad algorithm coverage General-purpose Linux administration, mod_ssl, NGINX on typical distributions
LibreSSL OpenBSD project Yes Focused feature set OpenBSD-centric deployments or reduced legacy compatibility layers
BoringSSL Google Yes Matches Chrome needs Chromium-derived stacks and Google-controlled releases tied to their consumers
Rustls Rustls contributors Yes Rust memory safety Rust applications and services that embed TLS without linking OpenSSL

When to Consider an Alternative

Choose BoringSSL when you ship software that must track Google’s TLS expectations or reuse Chromium networking stacks. Pick LibreSSL when your platform standardizes on OpenBSD releases and you accept a divergent command-line tool in some distributions. Prefer Rustls when you write Rust services and want a pure-Rust TLS implementation without OpenSSL linkage. Stay on OpenSSL for everyday Linux server certificate tasks, because documentation, distribution packages, and third-party integrations assume it.

Renewing and Automating Certificate Rotation

Manual renewal works for one or two servers. Beyond that, automate it. Two paths cover most deployments.

ACME automation with Certbot for public certificates from Let’s Encrypt or any ACME-compatible CA:

sudo certbot renew --dry-run

A successful dry run confirms that the systemd timer or cron job Certbot installs at setup will renew certificates before expiry. Confirm the timer is active:

sudo systemctl list-timers | grep certbot

Custom renewal script for internal CAs or certificates outside ACME. The script should:

  1. Generate a CSR from the same san.cnf used during issuance
  2. Submit the CSR to your CA endpoint or signing tool
  3. Atomically replace the certificate file in /etc/ssl/certs/
  4. Reload the consuming service after a successful config test
  5. Send a notification on success or failure

Run the script from a systemd timer rather than cron when possible. systemd timers log to the journal, support service dependencies, and can retry on transient failure.

Monitor expiry independently of the renewal job. A mismatched timer or a script that fails silently still leaves you with an expired certificate. A weekly cron job that pipes openssl x509 -enddate -noout output into your alerting pipeline catches the case where automation broke without anyone noticing.

FAQ

How do I generate a CSR using an existing private key instead of creating a new one?

Run openssl req with -key pointing at your PEM key and -new to emit a CSR file:

openssl req -key existing.key -new -out request.csr

Add -subj or -config when you want to skip prompts or attach SANs.

How do I add Subject Alternative Names (SANs) to a CSR?

Place SANs under [req_ext] in an OpenSSL config referenced by -config, or pass -addext 'subjectAltName=DNS:example.com,DNS:www.example.com' on OpenSSL 1.1.1 or newer:

openssl req -new -key domain.key -out domain.csr -config san.cnf

Confirm with openssl req -text -noout -in domain.csr.

How do I check when an SSL certificate expires?

Print the notAfter field:

openssl x509 -enddate -noout -in domain.crt

Use -dates when you need both notBefore and notAfter.

How do I verify that a private key matches a certificate?

For RSA, compare modulus hashes:

openssl rsa -noout -modulus -in domain.key | openssl md5
openssl x509 -noout -modulus -in domain.crt | openssl md5

Identical lines confirm the same key pair.

How do I decode and read the contents of a CSR?

Use req -text with -verify to validate structure:

openssl req -text -noout -verify -in domain.csr

SANs and the subject DN appear in the decoded text.

How do I convert a PEM certificate to PFX/PKCS12 format?

Export with pkcs12 -export, referencing the key, leaf cert, and optional chain:

openssl pkcs12 -export -inkey domain.key -in domain.crt -certfile ca-chain.pem -out domain.pfx

Add -legacy if an older importer rejects OpenSSL 3 defaults.

What is the difference between RSA 2048 and RSA 4096 for a private key?

RSA 4096 uses a larger modulus than RSA 2048, which increases asymmetric work during handshakes while offering additional security margin under classical threat models. RSA 2048 remains the common default for public TLS certificates today; choose RSA 4096 when policy explicitly requires it.

How do I use openssl s_client to test a live TLS connection?

Open a client connection and print negotiated parameters:

openssl s_client -connect example.com:443 -servername example.com

Append -tls1_2 or -tls1_3 to narrow protocol probes, and -showcerts to dump PEM chains.

Conclusion

This tutorial walked through OpenSSL workflows spanning RSA and EC private keys, CSR creation with SANs via config files and -addext, three patterns for self-signed certificates plus SAN coverage, PEM inspection commands, modulus matching checks, scheduled expiry queries, CA bundle verification, chain validation with intermediates, live TLS probing with openssl s_client, and conversions among PEM, DER, PKCS12, and PKCS7 containers.

You can now provision keys safely, submit CSRs that reflect modern hostname rules, validate certificates before enabling HTTPS, diagnose handshake behavior against remote ports, and export bundles for mixed Linux and Windows environments while accounting for OpenSSL 3 PKCS12 defaults.

Continue by automating issuance with Let’s Encrypt or your own CA, hardening web server TLS configurations, and linking application databases that require mutual TLS. Useful next reads:

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Mitchell Anicas
Mitchell Anicas
Author
See author profile

Software Engineer @ DigitalOcean. Former Señor Technical Writer (I no longer update articles or respond to comments). Expertise in areas including Ubuntu, PostgreSQL, MySQL, and more.

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Category:

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Great summary, was recently looking for exactly something like that. Thank you for the write up.

I recently stumbled across https://shaaaaaaaaaaaaa.com/ summarizing the soon to come retirement of SHA-1.

It may be of use also for this tutorial to add the option

 -sha256

to create the CSR.

I did that for a recent request from StartSSL and they offered a certificate accommodating the more secure requirements (passing also A+ on ssllabs.com)

Since you especially describe how to generate CSR for obtaining a certificate, it may be worth adding the option in order to be more future proof.

Best regards Sebastian

Great Summary. I want to know how can I add a key usage extension to a certificate. Specifically to make it act as a local-CA to sign other certificates?

I need to copy paste the Certificate Signing Request (CSR) how do i get a hold of it?

Too good. I was very happy after going through the articles. It helped me a lot. Especially the verification part.

Someone else created a csr request, and we got the final mail from CA which gave the X509 Certificates and intermediates only certificates. Now I am not sure that whether I am supposed to generate another private key based on the certificate, it would be great if you can explain about this part.

Thanks

The command provided in section “Generate a CSR from an Existing Certificate and Private Key” generates a file with the plaintext csr and encoded version:

Certificate Request:
    Data:
...
-----BEGIN CERTIFICATE REQUEST-----
MIICozCCAYsCAQAwXjEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVk

Is there any option to output only the encoded version?

Great article. Learnt a lot. Thanks for sharing.

Abbas

Mitchell - Fantastic post! Just one slight correction:

openssl verify -verbose -CAFile ca.crt domain.crt

The option uses a lowercase “f”, as in:

-CAfile

Thanks for this wonderful article, DO has always been of great article.

I am facing an issue with my SSL certificate installation, if you could help me.

I bought a Rapid SSL and used the below command to generate the .csr and .key files:

    sudo openssl req -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/server.key -out /etc/nginx/ssl/server.csr

I answered all questions which this command asked. I then provided the .csr to name.com and successfully generated the server/intermediate certificates. I then followed the steps mentioned at https://knowledge.rapidssl.com/support/ssl-certificate-support/index?page=content&actp=CROSSLINK&id=SO17664 and installed this certificate at my nginx server. I was able to open the HTTPS version of my site as well.

Now, to try something else, I run the command (sudo openssl req… ) again with different answers this time and regenerated a new server.key file. Unfortunately, I didn’t save the first server.key file. Post modification of nginx .conf file, when I tried to restart the server, I got the below error:

nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/nginx/ssl/server.key") failed (SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)

Seeing this error, I realized I have overwritten the server.key file. I tried to generate the key again with same answers I gave for the first time but still the key mismatch error is coming.

My nginx server is still running and I am able to access the HTTPS version of the site but my life is in trouble without the private key. I have gone through the below links but still stuck:

  1. http://stackoverflow.com/questions/26191463/ssl-error0b080074x509-certificate-routinesx509-check-private-keykey-values
  2. http://serverfault.com/questions/408112/nginx-ssl-certificate-issue-key-values-mismatch

I confirmed by running the below commands that my certificate (issued by name.com) and private keys don’t match:

openssl x509 -noout -modulus -in server_orig.cert | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5

Is there anything which I can do to find out the private key since Nginx is still up and running? In case not, should I get the certificate re-issued by Geotrust?

Any help would be deeply appreciated.

Thanks!

It will be very useful to explain creation of self-signed local CA pairs, signing CSR and install this CA certt on clients.

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Start building today

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.

Dark mode is coming soon.