By Mitchell Anicas and Vinayak Baranwal
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:
-subj, start under What Is OpenSSL and Why It Matters for SSL/TLS Management and Creating Certificate Signing Requests (CSRs).\) for readability; you can paste them as multi-line blocks or join them into one line.genrsa or genpkey, and EC keys with ecparam, including optional password encryption with AES-256.-newkey, from an existing key, from an existing certificate, or from an OpenSSL config that carries SANs for DNS names and IP addresses.-addext on OpenSSL 1.1.1 and newer.openssl rsa -text, and confirm that a key matches a cert or CSR using modulus hashes (RSA) or public key comparison (EC).notBefore and notAfter, verify a leaf against a CA file, and validate a chain with -untrusted intermediates per CA/Browser Forum Baseline Requirements practice.openssl s_client, including SNI, TLS 1.2 and TLS 1.3 probes (RFC 8446), and stapled OCSP status lines.-legacy compatibility when older clients cannot read modern defaults.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.
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
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.
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.
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.
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 |
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.
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 |
| 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.
CSRs encode the subject, public key, and requested extensions so a CA can issue a certificate that matches your DNS names and identity checks.
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.
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 |
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 |
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 |
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.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 |
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.
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 |
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 |
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 |
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.
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.
Use these commands to read the content of a certificate, CSR, or private key without converting the file first.
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
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
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.
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.
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.
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 |
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
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.Use openssl-s_client to reproduce client-side handshakes without a browser.
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 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.
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.
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.
| 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 |
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 |
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 |
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.
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 |
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 |
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 |
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 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 | 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 |
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.
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:
san.cnf used during issuance/etc/ssl/certs/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.
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.
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.
Print the notAfter field:
openssl x509 -enddate -noout -in domain.crt
Use -dates when you need both notBefore and notAfter.
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.
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.
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.
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.
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.
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.
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.
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.
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?
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:
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.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.