Cheat Sheets

OpenSSL Cheat Sheet: Keys, Certs and TLS Commands (PDF)

OpenSSL 3.5 makes a post-quantum ML-DSA key with the same genpkey command you use for RSA. And when you test a big site with s_client, the key exchange it reports is already the hybrid X25519MLKEM768, not plain X25519. Most OpenSSL cheat sheets still teach -nodes and genrsa, and none of the ones we checked show either of those things.

Original content from computingforgeeks.com - post 176790

This cheat sheet covers the OpenSSL commands admins actually type: keys, CSRs, self-signed and CA-signed certificates, inspection, format conversion, TLS testing, post-quantum algorithms and file encryption. Every command in the code blocks was run in October 2026 on Ubuntu 26.04 (OpenSSL 3.5.5) and Rocky Linux 10.2 (OpenSSL 3.5.8), and we note each place the two distros behave differently, mostly because of Rocky’s crypto-policies. The exact errors and their fixes are at the end, and the whole sheet is a one-page PDF.

Find the OpenSSL command for the job

Use this table when you know what you need but not the syntax. Each row is tested further down, with output from both distros:

JobCommand
Make an EC private keyopenssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.key
Make a post-quantum keyopenssl genpkey -algorithm ML-DSA-65 -out mldsa.key
CSR with SANsopenssl req -new -key ec.key -subj "/CN=web01.example.com" -addext "subjectAltName=DNS:web01.example.com" -out web01.csr
Self-signed cert, key unencryptedopenssl req -x509 -newkey rsa:2048 -noenc -keyout self.key -out self.crt -days 365 -subj "/CN=lab.example.com"
Read a certificateopenssl x509 -in self.crt -noout -subject -issuer -dates -ext subjectAltName
Will it expire within a day?openssl x509 -in self.crt -noout -checkend 86400
Does this key match this cert?Compare openssl x509 -in self.crt -noout -pubkey | openssl sha256 with openssl pkey -in self.key -pubout | openssl sha256
PEM to PKCS#12openssl pkcs12 -export -in self.crt -inkey self.key -out self.p12
Test a TLS serveropenssl s_client -connect example.com:443 -brief </dev/null
Expiry of a live siteecho | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate
Verify a chainopenssl verify -CAfile ca.crt web01.crt
Encrypt a fileopenssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in app.env -out app.env.enc
Random secretopenssl rand -base64 32

Check your OpenSSL version and providers

Almost every behaviour difference comes down to the version and the providers that are loaded, so check both first:

openssl version; openssl list -providers

Ubuntu 26.04 printed this, with only the default provider active, which is normal. Rocky Linux 10.2 printed the same apart from the 3.5.8 version string:

OpenSSL 3.5.5 27 Jan 2026 (Library: OpenSSL 3.5.5 27 Jan 2026)
Providers:
  default
    name: OpenSSL Default Provider
    version: 3.5.5
    status: active

Each distro ships the 3.5 long-term support line, which upstream supports until April 2030, and neither has moved to 4.0. Ubuntu’s 3.5.5 carries backported security fixes, so don’t compare its number with upstream’s latest 3.5 release. The legacy provider (old ciphers such as RC2, DES and MD4) is installed on both but not loaded, and that one fact explains the most common OpenSSL 3 error, covered below. On Ubuntu it lives in its own openssl-provider-legacy package, which the main library depends on, so apt always installs it.

Generate private keys

genpkey is the one command for every key type, so you don’t need to remember genrsa, ecparam and friends. We generated one of each kind on both distros:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out rsa.key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.key
openssl genpkey -algorithm ED25519 -out ed.key
openssl genpkey -algorithm ML-DSA-65 -out mldsa.key
openssl genpkey -algorithm ML-KEM-768 -out mlkem.key
openssl genpkey -algorithm SLH-DSA-SHA2-128s -out slh.key

The file sizes say a lot about the post-quantum trade-off. All keys came out with mode 0600 because genpkey creates them that way:

KeyUseFile size (bytes)
Ed25519Signatures119
SLH-DSA-SHA2-128sPost-quantum signatures, hash based168
EC P-256Signatures, ECDH241
RSA 3072Signatures, encryption2,484 (2,488 on Rocky)
ML-KEM-768Post-quantum key exchange only3,439
ML-DSA-65Post-quantum signatures5,604

RSA itself prints a line of dots and plus signs while it searches for primes, which is normal. The older genrsa still works, but since OpenSSL 3.0 it writes the same PKCS#8 format as genpkey. Our openssl genrsa -out g.key 2048 produced a file starting -----BEGIN PRIVATE KEY-----. If an old tool insists on BEGIN RSA PRIVATE KEY, convert it:

openssl pkey -in g.key -traditional -out g-rsa.key

To protect a key with a passphrase, or take one off, use pkey as well. Encryption writes BEGIN ENCRYPTED PRIVATE KEY, and a wrong passphrase fails with bad decrypt:

openssl pkey -in ec.key -aes256 -out ec-enc.key
openssl pkey -in ec-enc.key -out ec-plain.key

Read a key’s details with openssl pkey -in ec.key -text -noout, which starts with Private-Key: (256 bit) for P-256. Keep the -text output off shared screens, because it prints the private key material in hex.

Create a CSR with subject alternative names

Browsers and most clients ignore the CN and check only the SAN list, so a CSR without SANs is useless for TLS. -addext adds them on the command line, with no config file to edit:

openssl req -new -key ec.key \
  -subj "/CN=web01.example.com/O=Example" \
  -addext "subjectAltName=DNS:web01.example.com,DNS:www.example.com,IP:10.0.1.50" \
  -out web01.csr

Check the request’s own signature and the SANs before you send it anywhere:

openssl req -in web01.csr -noout -subject -verify
openssl req -in web01.csr -noout -text | grep -A1 'Subject Alternative Name'

Both distros confirmed the signature and listed all three names:

Certificate request self-signature verify OK
subject=CN=web01.example.com, O=Example
                X509v3 Subject Alternative Name:
                    DNS:web01.example.com, DNS:www.example.com, IP Address:10.0.1.50

Public CAs such as Let’s Encrypt only accept DNS names, and they read only the SAN list, so get it right here. Our Let’s Encrypt guide covers the automated route when you don’t need a hand-made CSR.

Make a self-signed certificate

One req -x509 command makes the key and the certificate together. Use -noenc to leave the key unencrypted, which is what a service that starts unattended needs:

openssl req -x509 -newkey rsa:2048 -noenc -keyout self.key -out self.crt -days 365 \
  -subj "/CN=lab.example.com" -addext "subjectAltName=DNS:lab.example.com"
openssl x509 -in self.crt -noout -subject -issuer -dates -ext subjectAltName

Subject and issuer match, which is what makes it self-signed:

subject=CN=lab.example.com
issuer=CN=lab.example.com
notBefore=Oct 22 01:04:15 2026 GMT
notAfter=Oct 22 01:04:15 2027 GMT
X509v3 Subject Alternative Name:
    DNS:lab.example.com

-noenc replaced -nodes in OpenSSL 3.0. The old flag is deprecated but still accepted, and it printed no warning on either distro, so old scripts keep working without telling you. Two other details are worth knowing:

  • the certificate is marked CA:TRUE by default, because the default openssl.cnf applies its v3_ca section to req -x509
  • -not_before 20261101000000Z -not_after 20271101000000Z sets exact dates instead of -days

The first one matters if you give the certificate to a strict client as a server certificate. Add -addext "basicConstraints=critical,CA:FALSE" for a plain leaf. For a certificate your browsers trust without warnings, mkcert installs a local CA for you, and we have an Ansible version of the self-signed workflow for fleets.

Sign certificates with your own CA

A two-file CA is enough for a lab, internal services or mutual TLS tests. Create the CA with the right extensions, then sign the CSR from earlier. -copy_extensions copy carries the SANs from the CSR into the certificate; without it they are dropped:

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc -keyout ca.key -out ca.crt -days 3650 \
  -subj "/CN=Example Lab Root CA" \
  -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl x509 -req -in web01.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 397 \
  -copy_extensions copy -out web01.crt

The signing step checks the CSR first, then the new certificate shows our CA as issuer and keeps all three SANs:

Certificate request self-signature ok
subject=CN=web01.example.com, O=Example
subject=CN=web01.example.com, O=Example
issuer=CN=Example Lab Root CA
X509v3 Subject Alternative Name:
    DNS:web01.example.com, DNS:www.example.com, IP Address:10.0.1.50

Verify the chain against the CA, and see what happens without it:

openssl verify -CAfile ca.crt web01.crt
openssl verify web01.crt

The first line passes; the second fails because our CA is not in the system trust store:

web01.crt: OK
CN=web01.example.com, O=Example
error 20 at 0 depth lookup: unable to get local issuer certificate
error web01.crt: verification failed

To make every tool on the box trust the CA, which we did and then undid on both distros, copy ca.crt into /usr/local/share/ca-certificates/ and run sudo update-ca-certificates on Ubuntu, or into /etc/pki/ca-trust/source/anchors/ and run sudo update-ca-trust on Rocky Linux. After that, a plain openssl verify web01.crt printed web01.crt: OK with no -CAfile. Be careful with this on a shared box: ca.key was made with -noenc, so anyone who can read it can mint certificates the whole system now trusts. Move it somewhere safe or encrypt it. To undo the trust, delete the file you copied and run sudo update-ca-certificates --fresh on Ubuntu or sudo update-ca-trust on Rocky; openssl verify web01.crt failed again for us afterwards. The bundles they build are /etc/ssl/certs/ca-certificates.crt on Ubuntu and /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem on Rocky, where the old /etc/pki/tls/certs/ca-bundle.crt is now a symlink to it.

Inspect certificates, CSRs and keys

x509 prints any field on its own, which beats scrolling through -text. These are the flags we use most:

CommandShows
openssl x509 -in c.crt -noout -textEverything
-subject -issuerWho it is for and who signed it
-dates / -enddateValidity window
-ext subjectAltName,basicConstraintsOnly the named extensions
-fingerprint -sha256Fingerprint to compare with a browser or pin
-checkend 86400Exit 1 if it expires within the given seconds
-serialSerial number, for revocation
openssl req -in r.csr -noout -text -verifyA CSR and its signature check

-checkend is the one to put in monitoring, because the exit code does the work. Our new certificate was good for a day but not for a full year:

openssl x509 -in self.crt -noout -checkend 86400; echo "checkend 1d exit=$?"
openssl x509 -in self.crt -noout -checkend 31536000; echo "checkend 365d exit=$?"

The text and the exit code agree:

Certificate will not expire
checkend 1d exit=0
Certificate will expire
checkend 365d exit=1

Matching a key to a certificate is the other daily job, usually after a renewal leaves several keys in one directory. The common -modulus trick only works for RSA, so compare a hash of the public key instead, which works for every key type:

openssl x509 -in self.crt -noout -pubkey | openssl sha256
openssl pkey -in self.key -pubout | openssl sha256
openssl pkey -in ec.key -pubout | openssl sha256

The certificate and its own key give the same hash; the unrelated EC key does not:

SHA2-256(stdin)= 8a3b597f011c9eb72cd17064b7f286481e46ee636d8d4e7a09db31461febe37e
SHA2-256(stdin)= 8a3b597f011c9eb72cd17064b7f286481e46ee636d8d4e7a09db31461febe37e
SHA2-256(stdin)= cd0ef2fe883d279dc85c57864c93912450fa8a2f8bb967132a3a8fe7db726356

For a deeper look at expiry checks, including bulk checks across many hosts, see our guide to checking certificate expiration with OpenSSL.

Convert between PEM, DER, PKCS#12 and PKCS#7

Windows, Java and network appliances each want a different container, and these commands cover the usual requests:

FromToCommand
PEM certDERopenssl x509 -in c.crt -outform DER -out c.der
DER certPEMopenssl x509 -in c.der -inform DER -out c.pem
PEM cert + keyPKCS#12 (.p12/.pfx)openssl pkcs12 -export -in c.crt -inkey c.key -out c.p12
PKCS#12PEM certopenssl pkcs12 -in c.p12 -nokeys -out c.crt
PKCS#12PEM key, unencryptedopenssl pkcs12 -in c.p12 -nocerts -noenc -out c.key
PEM certPKCS#7 (.p7b)openssl crl2pkcs7 -nocrl -certfile c.crt -out c.p7b
PKCS#7PEM certsopenssl pkcs7 -in c.p7b -print_certs -out certs.pem
PKCS#8 keyTraditional RSA keyopenssl pkey -in k.key -traditional -out k-rsa.key

The 3.5 tools also detect DER on input, so openssl x509 -in self.der -noout worked on both distros without -inform DER. Older guides still say it fails with Expecting: TRUSTED CERTIFICATE, and on 3.5 it doesn’t. PKCS#12 is where the 3.x change bites. A new .p12 uses AES-256 and a SHA-256 MAC, which pkcs12 -info shows plainly:

MAC: sha256, Iteration 2048
MAC length: 32, salt length: 8
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Certificate bag
PKCS7 Data
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256

Some older consumers can’t read that format: older Java builds, older macOS Keychain releases, some appliances. For those, export with -legacy, which uses a SHA-1 MAC, 40-bit RC2 for the certificate and 3DES for the key. The flip side is that OpenSSL 3 cannot read such a file without the same flag, and that produces the 0308010C error in the errors section.

openssl pkcs12 -export -legacy -in self.crt -inkey self.key -out legacy.p12

The legacy file reports MAC: sha1 and pbeWithSHA1And40BitRC2-CBC, which shows how weak it is. Send it only to the device that needs it.

Test TLS servers with s_client

s_client is the fastest way to answer what a server negotiates and what certificate it sends. -brief cuts the output to a summary, and </dev/null closes the connection instead of waiting for input:

openssl s_client -connect www.google.com:443 -servername www.google.com -brief </dev/null

On both distros, the default handshake with Google used the hybrid post-quantum group. Rocky printed the same lines as Ubuntu:

Connecting to 142.251.153.119
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=www.google.com
Hash used: SHA256
Signature type: ecdsa_secp256r1_sha256
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE

We passed -servername there out of habit, but SNI is sent automatically for a hostname; our runs without it negotiated the same group. -servername is only needed when you connect to an IP address or want a different name than the one you dial. The rest of the useful flags:

FlagUse
-showcertsPrint every certificate the server sends (3 for Google); this is not a verified chain
-tls1_2 / -tls1_3Force a version
-groups x25519Force a key exchange group
-verify_hostname nameCheck the certificate against a name
-CAfile ca.crtTrust a private CA for this test
-starttls smtpUpgrade a plain SMTP, IMAP, POP3 or FTP session first

Forcing a classic group shows the older label. With -groups x25519 the TLS 1.3 handshake printed Peer Temp Key: X25519, 253 bits instead of the negotiated group line, and the same label appears for TLS 1.2. Readers on OpenSSL 3.0, such as Ubuntu 24.04, see Server Temp Key in its place. Two one-liners cover most expiry and chain questions:

echo | openssl s_client -connect www.google.com:443 -servername www.google.com 2>/dev/null | openssl x509 -noout -subject -issuer -enddate
openssl s_client -connect www.google.com:443 -verify_hostname wrong.example.com -brief </dev/null

The first printed the subject, issuer=C=US, O=Google Trust Services, CN=WE2 and the expiry date. The second failed the name check with verify error:num=62:hostname mismatch, the same check a client makes when the SANs are wrong.

Use post-quantum algorithms

OpenSSL 3.5 added the three NIST post-quantum standards to the default provider: ML-KEM for key exchange, ML-DSA and SLH-DSA for signatures. List what your build has:

openssl list -signature-algorithms | grep -iE 'ml-dsa|slh-dsa'
openssl list -kem-algorithms | grep -iE 'ml-kem|mlkem'

The KEM list adds the hybrid KEMs to plain ML-KEM (excerpt). openssl list -tls1_3 -tls-groups shows which of them TLS can use: the pure MLKEM512, MLKEM768 and MLKEM1024, plus three of the four hybrids, X25519MLKEM768, SecP256r1MLKEM768 and SecP384r1MLKEM1024. X448MLKEM1024 is a KEM you can’t pass to -groups. Being a TLS group doesn’t mean a server accepts it, either: -groups MLKEM768 against Google ended in ssl/tls alert handshake failure on both distros, because Google only offers the hybrid:

  { 2.16.840.1.101.3.4.4.1, id-alg-ml-kem-512, ML-KEM-512, MLKEM512 } @ default
  { 2.16.840.1.101.3.4.4.2, id-alg-ml-kem-768, ML-KEM-768, MLKEM768 } @ default
  { 2.16.840.1.101.3.4.4.3, id-alg-ml-kem-1024, ML-KEM-1024, MLKEM1024 } @ default
  X25519MLKEM768 @ default
  X448MLKEM1024 @ default
  SecP256r1MLKEM768 @ default
  SecP384r1MLKEM1024 @ default

ML-DSA keys sign certificates like any other key. We made a self-signed ML-DSA certificate, served it with s_server and connected to it, so both the signature and the key exchange were post-quantum:

openssl req -x509 -key mldsa.key -out mldsa.crt -days 30 -subj "/CN=pq.example.com"
openssl s_server -accept 4433 -cert mldsa.crt -key mldsa.key -www >/dev/null 2>&1 &
sleep 1
openssl s_client -connect 127.0.0.1:4433 -CAfile mldsa.crt -brief </dev/null
kill $!

The sleep gives the server a moment to start listening, and kill $! stops exactly the server you started. The handshake worked on both distros (filtered to the relevant lines):

Protocol version: TLSv1.3
Signature type: mldsa65
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768

The cost shows up in size, not speed. The ML-DSA certificate was 7,529 bytes against 1,168 for RSA 2048, so every handshake carries several kilobytes more. Signing speed falls between RSA 2048 and RSA 3072, key generation is hundreds of times faster, and verification is the one area where RSA stays well ahead. We ran openssl speed -seconds 1 -signature-algorithms and -kem-algorithms on the same Ubuntu VM:

AlgorithmKeygen/sSign/s or encaps/sVerify/s or decaps/s
RSA 204817.81,81362,608
RSA 30725.959328,736
ML-DSA-448,063.61,4167,126
ML-DSA-654,4278974,665.7
X25519 (KEM)24,04012,017.226,583
ML-KEM-76818,359.629,50019,522
X25519MLKEM76810,1949,173.711,560

Those figures are from the Ubuntu VM. Rocky’s VM ran on a different physical host and its numbers moved in both directions; there ML-DSA-65 signed at 1,187 per second against 1,361 for RSA 3072, so treat the comparison as a rough guide, not a ranking. Only speed options run these: openssl speed ml-dsa-65 answered Unknown algorithm, so use the -signature-algorithms or -kem-algorithms switch. Both run every algorithm in the class, so expect a few minutes.

Know what Rocky Linux crypto-policies block

On Rocky Linux and RHEL, a system-wide crypto policy sits on top of OpenSSL’s own defaults, and it is the main reason the same command can work on Ubuntu and fail on Rocky. Check the active one with update-crypto-policies --show; ours was DEFAULT. We switched between policies on the lab VM and ran the same commands each time:

TestUbuntu 26.04Rocky DEFAULTRocky LEGACYRocky FUTURE
Sign a cert with -sha1Worksinvalid digestWorksNot tested
Serve a 1024-bit RSA certee key too smallee key too smallNot testedNot tested
Serve a 2048-bit RSA certWorksWorksNot testedee key too small
Group with www.google.comX25519MLKEM768X25519MLKEM768Not testedX25519MLKEM768
Force -tls1_1no protocols availableno protocols availableNot testedNot tested

Ubuntu has no crypto-policy layer by default, and its OpenSSL already runs at security level 2, which is why it also refuses 1024-bit keys and TLS 1.1. FUTURE raised the minimum RSA size to 3072 bits, so our 2048-bit lab certificate failed. It still reached Google, because Google offers the hybrid group FUTURE requires. Post-quantum key exchange is on in every predefined Rocky policy. To turn it off for one system, for instance when a middlebox chokes on the larger handshake, use the NO-PQ subpolicy:

sudo update-crypto-policies --set DEFAULT:NO-PQ
openssl s_client -connect www.google.com:443 -brief </dev/null 2>&1 | grep -E 'group|Temp Key'
sudo update-crypto-policies --set DEFAULT

Under DEFAULT:NO-PQ the handshake fell back to Peer Temp Key: X25519, 253 bits. Policy changes apply to new processes, so restart services (or reboot) after switching, and switch back to DEFAULT when you finish testing. If you need SHA-1 signatures for an old device, LEGACY re-enabled them for us, but it does not bring back TLS 1.0 or 1.1. Changing the policy for one old certificate weakens the whole box, so reissuing with SHA-256 is the better fix.

Hash, encode and encrypt files

OpenSSL doubles as a general crypto toolbox. These worked the same on both distros:

JobCommand
SHA-256 of a fileopenssl dgst -sha256 file
Hash stdinprintf 'text' | openssl sha256
Base64 encodeprintf 'hello' | openssl base64
Base64 decodeprintf 'aGVsbG8=\n' | openssl base64 -d
Random hex (16 bytes)openssl rand -hex 16
Random base64 (32 bytes)openssl rand -base64 32
Encrypt a fileopenssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in f -out f.enc
Decrypt itopenssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in f.enc -out f

Base64 decoding has a trap. openssl base64 -d expects a newline at the end of its input, so printf 'aGVsbG8=' | openssl base64 -d printed nothing at all, zero bytes, on both distros. Add the newline, or pass -A to read the input as one line, and you get hello back.

For enc, always pass -pbkdf2 and an iteration count. Without them OpenSSL derives the key the old, fast way and warns you about it:

*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

Decryption has to use the same -pbkdf2 -iter values, or it fails with bad decrypt. For secrets you share with people, age or GPG are friendlier, but enc is on every server already. If you want one book that explains the protocols behind all of these commands, Ivan Ristić’s Bulletproof TLS and PKI (paid link) (second edition, Feisty Duck) is the one we keep coming back to. It predates the post-quantum additions, so pair it with this page for those, but its chapters on PKI, certificate validation and server configuration are as useful as ever.

Fix common OpenSSL errors

These are the errors we triggered in the lab, with the text OpenSSL 3.5 printed. The long hex prefix on each error: line is a thread ID and changes every run, so search for the part after it.

Error: “error:0308010C:digital envelope routines::unsupported”

OpenSSL 3 cannot find an algorithm because it lives in the legacy provider, which is not loaded. Reading an old .pfx is the usual trigger:

Error outputting keys and certificates
40A72774F07D0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:../crypto/evp/evp_fetch.c:376:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()

Add -legacy to the pkcs12 command, or load both providers with -provider legacy -provider default; both read the file on both distros. Then re-export it without -legacy so the next person doesn’t hit the same wall. Node.js builds print the same code for an MD4 hash, and the cause there is the same.

Error: “Mac verify error: invalid password?”

The .p12 password is wrong. Our test with a wrong -passin printed exactly this. Check the password first. An old file with the right password gets past this check, because its SHA-1 MAC is still supported, and then fails with the 0308010C error above instead; that one needs -legacy.

Error: “unable to get local issuer certificate” (verify error 20)

OpenSSL can’t find the certificate that signed this one. For a private CA, pass it with -CAfile or add it to the system store as shown in the CA section. For a public site, the server is usually not sending its intermediate certificate; -showcerts shows what it sends, and the fix is to configure the full chain on the server.

Error: “self-signed certificate” (verify error 18)

openssl verify self.crt printed error 18 at 0 depth lookup: self-signed certificate. That is expected for a self-signed certificate nobody trusts yet. Trust it explicitly with -CAfile self.crt for a test, or sign real services from your own CA so clients trust one root instead of many leaves. Error 19, self-signed certificate in certificate chain, is the same problem one level up.

Error: “verify error:num=62:hostname mismatch”

The certificate is valid but not for the name you connected to. Check the SAN list with openssl x509 -noout -ext subjectAltName and reissue with the missing name in -addext subjectAltName. The CN is not checked when SANs are present.

Error: “SSL_CTX_use_certificate:ee key too small”

s_server refused to load a 1024-bit RSA certificate on both distros, and Rocky’s FUTURE policy refused a 2048-bit one with the same error. The key is below the security level: 2048 bits at the default level 2, 3072 under FUTURE. Reissue with a larger RSA key or switch to EC. On the verifying side, the same problem reads EE certificate key too weak.

Error: “do_sigver_init:invalid digest” on Rocky Linux

Rocky’s DEFAULT crypto policy forbids SHA-1 signatures, so openssl req -x509 -sha1 failed with error:03000098:digital envelope routines:do_sigver_init:invalid digest and exit 1. The same command worked on Ubuntu. Drop -sha1 and let OpenSSL pick SHA-256. Only switch to the LEGACY policy if a device you can’t replace insists on SHA-1.

Error: “no protocols available”

You forced a protocol version the security level forbids. -tls1_1 printed tls_setup_handshake:no protocols available on both distros after the TCP connection opened but before any TLS record was sent. That is OpenSSL refusing on your side, not the server. If you really need to test a TLS 1.1 server, only a throwaway client with -cipher DEFAULT@SECLEVEL=0 will try.

Error: “wrong version number” or “packet length too long”

You spoke TLS to a port that answers in plain text. We pointed s_client at nginx on port 80, and the two distros reported it differently: Rocky printed tls_validate_record_header:wrong version number and Ubuntu printed tls_get_more_records:packet length too long. Both mean the same thing. Use the TLS port, or -starttls smtp (or imap, pop3, ftp) for services that upgrade a plain connection.

Error: “Could not find certificate from file”

The file is not a certificate. We got Could not find certificate from web01.csr for a CSR, and the same for a private key, each followed by a STORE routines unsupported line. A file of junk printed the first line alone. Check the first line with head -1, because the header tells you which command the file needs:

  • BEGIN CERTIFICATE: openssl x509
  • BEGIN CERTIFICATE REQUEST: openssl req
  • BEGIN PRIVATE KEY or BEGIN ENCRYPTED PRIVATE KEY: openssl pkey
  • no header at all: DER, which 3.5 reads directly

A missing file reads Could not open file or uri for loading certificate from nonexistent.pem: No such file or directory instead.

Error: “key values mismatch”

The certificate and key don’t belong together. Signing with the wrong CA key printed CA certificate and CA private key do not match followed by this line. Other commands word the same problem differently: pkcs12 -export printed No cert in -in file 'web01.crt' matches private key, and s_server printed Private key does not match the certificate public key. Compare the public key hashes as shown in the inspection section to find the right pair.

For the commands around these, such as moving keys between servers or locking down their directories, our SSH cheat sheet and Linux commands cheat sheet for Ubuntu and Rocky Linux cover the rest. If you need a newer OpenSSL than your distro ships, we have guides to building OpenSSL on Ubuntu and on Rocky Linux.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) AI Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) wslc Cheat Sheet: WSL Containers Commands vs Docker (PDF) Cheat Sheets wslc Cheat Sheet: WSL Containers Commands vs Docker (PDF) Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) Security Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) How To Configure FreeIPA Replication on Rocky Linux / AlmaLinux 10 and 9 Security How To Configure FreeIPA Replication on Rocky Linux / AlmaLinux 10 and 9

Leave a Comment

Press ESC to close