openssl <command> [options]
openssl version -a
OpenSSL is the Swiss Army knife of cryptography on the command line. Sysadmins use it to create keys and certificates, read and check them, test HTTPS and mail servers, convert file formats,
hash and sign files, encrypt data, and generate random passwords. This is a command REFERENCE. For step-by-step certificate creation (keys, CSRs, your own CA, Let's Encrypt) see the
"Creating TLS Certificates" guide. Examples use OpenSSL 3.x. Most work on 1.1.1 too, and differences are marked. Check yours with openssl version .
Never paste private keys into websites or chat, never send a key file to a CA, and keep key files at mode 600.
Help and information
openssl version # the version openssl version -a build details openssl version -d the config directory (OPENSSLDIR)
openssl help # list all commands openssl help x509 options for one command (also: man openssl-x509 or man x509 )
openssl list -commands # every command openssl list -cipher-algorithms symmetric ciphers openssl list -digest-algorithms hash functions
openssl list -public-key-algorithms # RSA, EC, Ed25519... openssl list -providers OpenSSL 3 providers openssl list -kdf-algorithms
openssl ciphers -v # every TLS cipher suite the library knows, with details openssl ciphers -v 'ECDHE+AESGCM' filter them openssl ciphers -s -tls1_3 -v TLS 1.3 suites
openssl ecparam -list_curves # elliptic curves available openssl errstr 0x0308010C explain an OpenSSL error code
openssl speed -evp aes-256-gcm # benchmark a cipher openssl speed rsa2048 ecdsap256 benchmark signatures
Verbs and files: most commands take -in FILE (input), -out FILE (output), -noout (do not print the encoded data), and -text (print it human-readable). If -in is missing they read standard input.
Common option patterns
-in file / -out file Input and output files (default: stdin and stdout)
-inform PEM|DER -outform PEM|DER Input and output format (PEM is text, DER is binary)
-noout Do not print the base64 blob -text Print details as text
-passin pass:SECRET | file:/path | env:VARNAME | stdin How to supply the passphrase of the INPUT key -passout ... passphrase for the output
-nodes (OpenSSL 1.1) / -noenc (OpenSSL 3) Do not encrypt the output private key
-sha256 / -sha384 / -sha512 Hash to use -days N Validity in days -subj "/C=US/O=Org/CN=host" Set the subject without prompts
-addext "subjectAltName=DNS:a.example.com,IP:10.0.0.5" Add an extension (OpenSSL 1.1.1+ for req/x509) -config file Use a specific config file
-legacy OpenSSL 3: load the legacy provider (for old algorithms such as RC2 or MD4 in old PKCS#12 files)
Passing secrets: pass:SECRET puts the password in your shell history and the process list. Prefer file: (mode 600) or env: , or type it at the prompt.
Private keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out key.pem # RSA key (the modern command) openssl genrsa -out key.pem 3072 (older)
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem # ECDSA P-256 key (a great default) openssl ecparam -name prime256v1 -genkey -noout -out key.pem (older)
openssl genpkey -algorithm ED25519 -out ed.pem # Ed25519 (used for signing, SSH-style keys, not for public TLS certificates)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -aes-256-cbc -out key.pem # encrypted with a passphrase (asks for it)
chmod 600 key.pem # ALWAYS
openssl pkey -in key.pem -noout -text # inspect any key type openssl rsa -in key.pem -check -noout check an RSA key openssl ec -in key.pem -noout -text
openssl pkey -in key.pem -pubout -out pub.pem # extract the PUBLIC key
openssl pkey -in key.pem -aes256 -out key-enc.pem # add a passphrase openssl pkey -in key-enc.pem -out key-plain.pem remove it (asks for it)
openssl pkcs8 -topk8 -nocrypt -in rsa-old.pem -out key-pkcs8.pem # convert a traditional key ("BEGIN RSA PRIVATE KEY") to PKCS#8 ("BEGIN PRIVATE KEY")
openssl rsa -in key.pem -traditional -out key-trad.pem # back to the traditional RSA format (OpenSSL 3)
Key file headers tell you the format: BEGIN PRIVATE KEY (PKCS#8) BEGIN RSA PRIVATE KEY (old RSA) BEGIN EC PRIVATE KEY (old EC) BEGIN ENCRYPTED PRIVATE KEY (PKCS#8 with a passphrase) BEGIN OPENSSH PRIVATE KEY (SSH key: use ssh-keygen, not openssl)
Certificate signing requests (CSRs)
openssl req -new -newkey rsa:3072 -nodes -keyout key.pem -out req.csr -subj "/CN=www.example.com" -addext "subjectAltName=DNS:www.example.com,DNS:example.com" # new key AND CSR (OpenSSL 3: -noenc instead of -nodes)
openssl req -new -key key.pem -out req.csr -config server.cnf # CSR from an existing key and a config file
openssl req -in req.csr -noout -text # read a CSR (check the names before sending)
openssl req -in req.csr -noout -verify # verify its signature openssl req -in req.csr -noout -subject openssl req -in req.csr -noout -pubkey
openssl req -in req.csr -noout -ext subjectAltName # the SAN in the request
Always list every name in subjectAltName. Modern clients ignore the CN.
Certificates: create, read, convert
openssl x509 -in cert.pem -noout -text # everything about a certificate
openssl x509 -in cert.pem -noout -subject -issuer -serial -dates # the short version -startdate -enddate just the dates
openssl x509 -in cert.pem -noout -ext subjectAltName # names covered -ext keyUsage,extendedKeyUsage,basicConstraints more extensions
openssl x509 -in cert.pem -noout -fingerprint -sha256 # fingerprint (SHA-256, the usual) -fingerprint -sha1 older style
openssl x509 -in cert.pem -noout -pubkey # public key openssl x509 -in cert.pem -noout -modulus RSA modulus (older key matching)
openssl x509 -in cert.pem -noout -ocsp_uri # the OCSP responder address -noout -issuer_hash hash used for CA directories
openssl x509 -in cert.pem -noout -checkend 2592000 # exit code 1 if it expires within 30 days (2592000 seconds); 0 if not
openssl x509 -in cert.pem -noout -dates -checkend 0 # already expired?
openssl x509 -in cert.pem -outform DER -out cert.der # PEM to DER openssl x509 -in cert.der -inform DER -out cert.pem DER to PEM
openssl x509 -req -in req.csr -signkey key.pem -days 365 -out selfsigned.crt # self-sign a CSR (add an -extfile with the SAN, see the certificate guide)
openssl req -x509 -newkey rsa:3072 -nodes -keyout k.pem -out c.pem -days 365 -subj "/CN=host" -addext "subjectAltName=DNS:host" # one command: key + self-signed certificate
openssl x509 -req -in req.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 397 -sha256 -extfile server.ext # sign with your CA
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name" # a quick SAN check with grep
Extract certificates from a file that contains several (a chain or bundle):
openssl storeutl -certs -noout fullchain.pem # list what is inside (OpenSSL 3)
awk 'BEGIN{c=0} /BEGIN CERT/{c++} {print > "cert-" c ".pem"}' fullchain.pem # split a chain into cert-1.pem, cert-2.pem...
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout # print every subject and issuer in the bundle
Read the same file as a chain in one command: openssl x509 -in fullchain.pem -noout -subject shows only the FIRST certificate. Use the split above for the rest.
Match a key, a certificate and a CSR
openssl x509 -in cert.pem -noout -pubkey | openssl sha256
openssl pkey -in key.pem -pubout | openssl sha256 # all three hashes must be identical
openssl req -in req.csr -noout -pubkey | openssl sha256
For RSA only (older habit): openssl x509 -noout -modulus -in cert.pem | openssl md5 openssl rsa -noout -modulus -in key.pem | openssl md5 openssl req -noout -modulus -in req.csr | openssl md5
A mismatch is the cause of "key values mismatch" on server start.
Verify certificates and chains
openssl verify -CAfile ca.crt server.crt # a certificate signed directly by a known CA
openssl verify -CAfile root.crt -untrusted intermediate.crt server.crt # with an intermediate in between
openssl verify -CApath /etc/ssl/certs -untrusted chain.pem server.crt # against the system trust store (RHEL: /etc/pki/tls/certs)
openssl verify -CAfile ca.crt -crl_check -CRLfile ca.crl server.crt # also check a revocation list (add -crl_download to fetch it)
openssl verify -purpose sslserver -CAfile ca.crt server.crt # check it is allowed to be a server certificate (also sslclient, smimesign)
openssl verify -attime 1767225600 -CAfile ca.crt server.crt # check validity at a given time (Unix time)
Output "server.crt: OK" means the chain is fine. Errors show the failing depth, such as error 20 ... unable to get local issuer certificate (a missing intermediate or root).
Test TLS servers: s_client
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null # connect and show the handshake (-servername sends SNI: required on shared hosts)
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null # print every certificate in the chain the server sent
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates # the leaf certificate's details
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -outform PEM > host.pem # download the certificate
openssl s_client -connect host:443 -servername host -brief </dev/null # short summary (TLS version, cipher, verification) (1.1.1+)
openssl s_client -connect host:443 -tls1_2 </dev/null # force a TLS version: -tls1_3 -tls1_2 -tls1_1 -tls1 (old versions may be compiled out)
openssl s_client -connect host:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' </dev/null # test a specific TLS 1.2 cipher -ciphersuites TLS_AES_256_GCM_SHA384 TLS 1.3
openssl s_client -connect host:443 -CAfile ca.crt </dev/null # verify against your own CA -verify_return_error fail on any verification problem
openssl s_client -connect host:443 -cert client.crt -key client.key </dev/null # present a client certificate (mutual TLS)
openssl s_client -connect host:443 -status </dev/null | grep -A10 "OCSP response" # is OCSP stapling working?
openssl s_client -connect host:443 -alpn h2 </dev/null | grep ALPN # negotiate HTTP/2
openssl s_client -connect host:443 -servername host -tlsextdebug </dev/null # more handshake detail
STARTTLS protocols (upgrade a plain connection to TLS):
openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com </dev/null # also: imap (143) pop3 (110) ftp xmpp ldap postgres mysql
openssl s_client -connect mail.example.com:465 </dev/null # implicit TLS (SMTPS, IMAPS 993, POP3S 995, LDAPS 636)
Read the result: "Verification: OK" (newer) or "Verify return code: 0 (ok)" means the chain verified. Other codes: 10 expired, 18 self-signed, 19 self-signed in chain, 20 unable to get local issuer, 21 unable to verify the first certificate, 62 hostname mismatch (with -verify_hostname).
Check a name matches: openssl s_client -connect host:443 -servername host -verify_hostname host </dev/null 2>&1 | grep -i verif
Type Q and Enter, or use </dev/null, or add -quiet so the command does not wait for input.
Run a test server: s_server
openssl s_server -accept 4433 -cert server.crt -key server.key -www # a tiny HTTPS server showing TLS details at https://localhost:4433/ (Ctrl+C to stop)
openssl s_server -accept 4433 -cert server.crt -key server.key -cert_chain chain.pem -tls1_3
openssl s_server -accept 4433 -cert server.crt -key server.key -Verify 1 -CAfile ca.crt # REQUIRE a client certificate
Good for checking a certificate and a client without setting up nginx. Test it with curl --cacert ca.crt https://localhost:4433/ or openssl s_client -connect localhost:4433 .
Time a server: openssl s_time -connect host:443 -new -time 10 (handshakes per second for 10 seconds)
Convert and bundle: PKCS#12, PKCS#7, PKCS#8, JKS
openssl pkcs12 -export -out bundle.p12 -inkey key.pem -in cert.pem -certfile chain.pem -name "www.example.com" # key + certificate + chain into one .p12/.pfx (asks for an export password)
openssl pkcs12 -in bundle.p12 -nodes -out all.pem # everything to PEM (OpenSSL 3: -noenc) asks for the import password
openssl pkcs12 -in bundle.p12 -nokeys -clcerts -out cert.pem # only the certificate -nokeys -cacerts only the chain
openssl pkcs12 -in bundle.p12 -nocerts -nodes -out key.pem # only the private key
openssl pkcs12 -info -in bundle.p12 -noout # what is inside, and which algorithms it uses -legacy for old files on OpenSSL 3
openssl pkcs12 -export -legacy -out old.p12 -inkey key.pem -in cert.pem # an older-format file for very old Windows or Java
openssl crl2pkcs7 -nocrl -certfile chain.pem -out chain.p7b # PEM chain to PKCS#7 (.p7b) openssl pkcs7 -in chain.p7b -print_certs -out chain.pem back to PEM
keytool -importkeystore -srckeystore bundle.p12 -srcstoretype PKCS12 -destkeystore keystore.jks # Java (keytool is part of the JDK)
Hint: a .cer or .crt file can be PEM or DER. Look at it: head -1 file (PEM starts with -----BEGIN) or file file .
Hashes, HMACs and checksums: dgst
openssl dgst -sha256 file.iso # SHA-256 of a file (same as sha256sum file.iso, in a slightly different format) -sha512 -sha1 -md5 other hashes (md5 and sha1 are for checksums only, not security)
openssl dgst -sha256 -r file.iso # -r prints in the "hash filename" format that sha256sum uses
echo -n "text" | openssl dgst -sha256 # hash a string (echo -n: no trailing newline changes the result!)
openssl dgst -sha256 -hmac "secretkey" file # HMAC (keyed hash) for message authentication openssl dgst -sha256 -mac HMAC -macopt hexkey:00ff... file
openssl dgst -sha256 -binary file | openssl base64 # the hash in base64 (used by some APIs and SRI hashes) openssl dgst -sha384 -binary file.js | openssl base64 -A for Subresource Integrity
openssl list -digest-algorithms # what your build supports
Sign and verify files (digital signatures)
openssl dgst -sha256 -sign key.pem -out file.sig file.txt # sign a file with a private key (RSA or ECDSA)
openssl dgst -sha256 -verify pub.pem -signature file.sig file.txt # verify with the public key prints "Verified OK" or "Verification failure"
openssl pkeyutl -sign -inkey ed.pem -rawin -in file.txt -out file.sig # Ed25519 signing (uses pkeyutl; needs OpenSSL 3 for -rawin)
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in file.txt -sigfile file.sig
Distribute the PUBLIC key (pub.pem) and the signature, keep the private key secret. Anyone can now check the file was not altered and came from you.
Encrypt and decrypt with a password: enc
openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in secret.txt -out secret.enc # encrypt (asks for a password twice)
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in secret.enc -out secret.txt # decrypt (use the SAME options)
openssl enc -aes-256-cbc -pbkdf2 -a -in secret.txt -out secret.b64 # -a makes the output text (base64) so you can paste or email it
openssl enc -aes-256-cbc -pbkdf2 -pass file:pw.txt -in f -out f.enc # password from a file (mode 600)
openssl enc -list # the ciphers supported
Important limits of openssl enc : no authentication (a modified file can decrypt to garbage without a warning), no support for AEAD modes like AES-GCM, and old options without -pbkdf2 are weak.
For encrypting files for real use, prefer age (age -p -o file.age file) or gpg --symmetric or an encrypted volume. Use enc for quick interoperability and tests.
Encrypt to someone's PUBLIC key (hybrid): use openssl cms -encrypt or openssl smime -encrypt (below), not raw RSA on large files.
Random data, passwords and encoding
openssl rand -base64 32 # 32 random bytes in base64: a strong password, API token or secret key
openssl rand -hex 16 # 16 random bytes as 32 hex characters (a session ID or a salt)
openssl rand -out random.bin 1024 # 1 KB of random bytes into a file
openssl rand -base64 24 | tr -d '/+=' | cut -c1-20 # a 20-character alphanumeric password
openssl passwd -6 'MyPassword' # a SHA-512 crypt hash for /etc/shadow (prompts if you leave the password off; -6 = SHA-512, -5 = SHA-256) -salt abc123 fixed salt
openssl passwd -apr1 'MyPassword' # an Apache htpasswd MD5 hash: echo "alice:$(openssl passwd -apr1 'pass')" >> .htpasswd (use bcrypt via htpasswd -B for new setups)
openssl base64 -e -in file -out file.b64 # encode openssl base64 -d -in file.b64 -out file decode (-A = one line, no wrapping)
echo -n "hello" | openssl base64 # aGVsbG8= echo aGVsbG8= | openssl base64 -d
openssl enc -base64 -d <<< "aGVsbG8=" # decode from a string (bash here-string)
openssl rand -hex 4 | xargs -I{} echo "Ticket-{}" # generate a random ID
Random numbers here come from the system's secure random source and are suitable for keys and tokens.
Revocation: OCSP and CRLs
openssl x509 -in cert.pem -noout -ocsp_uri # where to ask
openssl ocsp -issuer chain.pem -cert cert.pem -url http://ocsp.example-ca.com -resp_text -noverify # ask the CA whether the certificate is good/revoked (chain.pem must hold the ISSUER; use -CAfile to verify the answer)
openssl x509 -in cert.pem -noout -text | grep -A4 "CRL Distribution" # the CRL address
curl -s http://crl.example-ca.com/ca.crl -o ca.crl
openssl crl -in ca.crl -inform DER -noout -text | head -20 # read a CRL (use -inform PEM if it is PEM)
openssl crl -in ca.crl -inform DER -noout -nextupdate # when it expires
Running your own CA: openssl ca -config ca.cnf -in req.csr -out cert.pem -notext -batch revoke: openssl ca -config ca.cnf -revoke cert.pem new CRL: openssl ca -config ca.cnf -gencrl -out ca.crl (see the certificate guide)
S/MIME and CMS: sign and encrypt email or files with certificates
openssl smime -sign -in msg.txt -signer cert.pem -inkey key.pem -out msg.signed # sign a message openssl smime -verify -in msg.signed -CAfile ca.crt verify
openssl smime -encrypt -aes256 -in msg.txt -out msg.enc recipient.pem # encrypt to the recipient's certificate openssl smime -decrypt -in msg.enc -recip cert.pem -inkey key.pem
openssl cms -sign -in file -signer cert.pem -inkey key.pem -out file.cms -outform DER # the modern CMS format, works for any file openssl cms -verify -in file.cms -inform DER -CAfile ca.crt
openssl cms -encrypt -aes256 -in file -out file.cms -outform DER recipient.pem # encrypt any file to a certificate openssl cms -decrypt -in file.cms -inform DER -recip cert.pem -inkey key.pem
Diffie-Hellman and other parameters
openssl dhparam -out dhparam.pem 2048 # DH parameters (only needed for old configs with plain DHE). With ECDHE and TLS 1.3 you do not need them. Add -dsaparam to make them quickly (less standard)
openssl ecparam -name prime256v1 -out ec-params.pem # named elliptic-curve parameters openssl ecparam -in ec-params.pem -genkey -noout -out key.pem
openssl prime 17 # is it prime? openssl prime -generate -bits 64 make a random prime
openssl asn1parse -in cert.pem -i # dump the ASN.1 structure of any PEM or DER file (great for debugging strange certificates) openssl asn1parse -inform DER -in file.der
Configuration, providers and environment
openssl version -d # the config directory the main file is usually /etc/ssl/openssl.cnf (Debian/Ubuntu) or /etc/pki/tls/openssl.cnf (RHEL)
OPENSSL_CONF=/path/my.cnf openssl req ... # use a different config for one command openssl req -config my.cnf ...
SSL_CERT_FILE=/path/ca-bundle.crt SSL_CERT_DIR=/path/certs # tell OpenSSL-based programs (curl, Python, Ruby, git) which trust store to use
OpenSSL 3 providers: default (modern algorithms), legacy (old ones like RC2, DES, MD4), fips (validated modules). Use -provider legacy -provider default to load old algorithms for one command.
Minimal config for a CSR with SAN (server.cnf):
[req]
prompt = no
distinguished_name = dn
req_extensions = ext
[dn]
CN = www.example.com
[ext]
subjectAltName = DNS:www.example.com,DNS:example.com
Use it: openssl req -new -key key.pem -out req.csr -config server.cnf
Version differences to know
OpenSSL 1.1.1 to 3.0: -nodes is now -noenc (the old spelling still works, with a warning in some builds). Many older algorithms moved to the "legacy" provider
Old PKCS#12 files (RC2/3DES) fail with "unsupported" or "error:0308010C": add -legacy
Deprecated commands: rsautl (use pkeyutl), genrsa/rsa/ec (still work, but genpkey and pkey are preferred), -md5 defaults gone. dsa and 3DES are legacy
Default key format for genpkey is PKCS#8. Traditional RSA/EC files need -traditional
LibreSSL (macOS, some BSDs and Alpine older builds) is a fork with fewer features and slightly different flags: openssl version shows it. Install a full OpenSSL with brew install openssl@3 , and call it by its full path.
macOS: the built-in /usr/bin/openssl is LibreSSL. For -addext, -ext, SHA-3, and -noenc use Homebrew's OpenSSL: /opt/homebrew/opt/openssl@3/bin/openssl (Apple silicon) or /usr/local/opt/openssl@3/bin/openssl (Intel).
Practical one-liners
# When does the certificate of a live site expire?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate
# Days until it expires (GNU date)
end=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2); echo $(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 )) days
# Alert if a certificate file expires within 30 days
openssl x509 -in cert.pem -noout -checkend 2592000 || echo "renew soon"
# Which names does a live certificate cover?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -ext subjectAltName
# Who issued a website's certificate?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer
# Save the whole chain a server sends into a file
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | awk '/BEGIN CERT/,/END CERT/' > chain.pem
# Which TLS versions does a server accept?
for v in tls1 tls1_1 tls1_2 tls1_3; do printf '%s: ' $v; openssl s_client -connect example.com:443 -servername example.com -$v </dev/null 2>&1 | grep -q "Cipher is" && echo yes || echo no; done
# Do the key and certificate match?
[ "$(openssl x509 -in cert.pem -noout -pubkey | openssl sha256)" = "$(openssl pkey -in key.pem -pubout | openssl sha256)" ] && echo MATCH || echo MISMATCH
# Fingerprint of a certificate (to compare with what a browser shows)
openssl x509 -in cert.pem -noout -fingerprint -sha256
# A quick self-signed certificate for a lab
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout lab.key -out lab.crt -days 365 -subj "/CN=lab.local" -addext "subjectAltName=DNS:lab.local,IP:192.168.1.10"
# Convert a Windows .pfx to PEM key and certificate
openssl pkcs12 -in site.pfx -nocerts -nodes -out site.key ; openssl pkcs12 -in site.pfx -clcerts -nokeys -out site.crt
# Generate a strong random password
openssl rand -base64 24
# Hash a password for /etc/shadow (SHA-512)
openssl passwd -6
# Checksum an ISO and compare
openssl dgst -sha256 debian.iso ; echo "EXPECTED debian.iso" | sha256sum -c
# Encrypt a file for yourself with a password (quick and dirty)
openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in notes.txt -out notes.txt.enc
# See the SAN and details of a CSR before you send it
openssl req -in req.csr -noout -text | grep -E "Subject:|DNS:|IP Address:|Public-Key"
# Test an SMTP server's STARTTLS certificate
openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates
# Check that a private key is not encrypted (no passphrase needed)
openssl pkey -in key.pem -noout && echo "unencrypted" || echo "encrypted or invalid"
Common problems and errors
"unable to load private key" / "no start line" The file is not PEM (it may be DER or PKCS#12), has extra text at the top, or a Windows line ending. Check the first line, and try -inform DER
"bad decrypt" / "bad password read" Wrong passphrase, wrong cipher or iteration options for enc, or a damaged file
"unsupported" / "error:0308010C:digital envelope routines::unsupported" OpenSSL 3 cannot read an old-algorithm file (usually PKCS#12). Add -legacy (and re-create the file with modern algorithms)
"key values mismatch" The certificate and key do not belong together. Compare the pubkey hashes
"Error opening Private Key file" / "Permission denied" Wrong path, or the file mode blocks you. Check with ls -l
"unable to get local issuer certificate" (verify error 20) The chain is incomplete or the CA is not trusted. Provide the intermediate (-untrusted) or the CA (-CAfile)
"self signed certificate in certificate chain" (verify error 19) A private CA is in the chain. Trust it with -CAfile ca.crt
"certificate has expired" (verify error 10) Expired, or the local clock is wrong. Check both the certificate dates and date
"Verify return code: 62 (hostname mismatch)" The name you connected to is not in the certificate's SAN
s_client hangs and waits It is waiting for input. Add </dev/null , type Q, or use -quiet
"connect:errno=111" / "Connection refused" Nothing is listening on that port, or a firewall blocks it. Test with nc -zv host port
"SSL routines::tlsv1 alert protocol version" / "no protocols available" The server or your OpenSSL build does not support the TLS version you forced. Try -tls1_2 or -tls1_3
"wrong version number" / "unknown protocol" You connected to a plain (non-TLS) port, or you need -starttls for that service
"no peer certificate available" The server sent no certificate. Wrong SNI (add -servername), or the protocol needs STARTTLS
"-addext" or "-ext" is not recognized (unknown option) Your OpenSSL is too old (before 1.1.1) or it is LibreSSL. Use a config file with req_extensions and -extfile, or install a newer OpenSSL
"req: Unknown option nodes" or a warning about -nodes OpenSSL 3 prefers -noenc. Either works on most builds. Use whichever your version accepts
"Can't open ... openssl.cnf for reading" A wrong or missing config path. Check openssl version -d , or set OPENSSL_CONF, or pass -config
enc output cannot be decrypted The decrypt command must use the SAME cipher, -pbkdf2, -iter and -md options as the encrypt command
Extra newline changes a hash Use echo -n (or printf) when hashing strings. echo adds a newline
When a certificate misbehaves, run openssl x509 -noout -text on it and openssl s_client -connect host:443 -servername host on the live server. Between the two you see what is really being served.
Always pass -servername to s_client (SNI), and use </dev/null so it exits instead of waiting.
Match key and certificate by comparing the hashes of their public keys. Do this whenever a server will not start after a renewal.
Use genpkey and pkey for keys, and put every name in subjectAltName. Keep key files at mode 600 and never paste them anywhere.
Prefer ECDSA P-256 for new server keys, SHA-256 or better for signatures, and TLS 1.2 or 1.3 for tests.
On OpenSSL 3, "unsupported" errors on old .p12 files mean add -legacy. On macOS use Homebrew's OpenSSL, not the built-in LibreSSL, when you need newer options.
openssl enc has no integrity check. For encrypting files for real use, choose age or gpg. For hashes, dgst matches sha256sum.
Avoid putting passwords on the command line (pass:). Use file: or env: or the prompt.
Use openssl rand -base64 32 for strong random secrets, and openssl passwd -6 for shadow-style password hashes.
Read the man pages: man openssl-req, man openssl-x509, man openssl-s_client. Option names change slightly between versions.