Cheat Sheet

OpenSSL Commands Cheat Sheet

OpenSSL is the command line toolkit behind most TLS work on Linux, macOS, and Windows (it ships with Git for Windows). This sheet covers the openssl commands you actually type: reading and checking certificates, generating keys and CSRs, converting between PEM, DER, and PFX, and the odd hashing and encryption jobs.

Last updated September 11, 2026

Inspect a Certificate

stops OpenSSL echoing the PEM block after the fields you asked for. For a paste-and-read version, use the certificate decoder.

CommandWhat it does
Print everything: subject, issuer, validity, SANs, key type, extensions
Just the notBefore and notAfter lines
Expiry date only
Exit 1 if it expires within 30 days (value is seconds), for scripts
Who the certificate is for ( for who signed it)
The DNS names it covers
Serial number
SHA-256 fingerprint ( for the old style)
Extract the public key as PEM
Read a binary DER file ( from Windows is often DER)
What the certificate may be used for (server, client, CA)
The key usage values the CA set
Dump the raw ASN.1 structure ( for a binary file)

Read every certificate in a chain file, not just the first:

openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout

Check a Live Server

opens a TLS connection and prints the handshake. Always pass (SNI), or a host serving several sites hands you the default certificate instead of the one you wanted. The closes the session instead of leaving it waiting for input. The SSL checker runs the same check from a browser.

CommandWhat it does
Handshake, certificate, chain, and cipher for a site
Print every certificate the server sends, in PEM
Test a STARTTLS service (, , , , , also work)
Force one protocol version (, or to exclude)
Verify against a specific CA bundle (internal CAs)
Fail the connection on a verify error instead of continuing
Test a server by IP before DNS changes, with the right SNI
Check HTTP/2 negotiation

Print just the dates or the subject of the live certificate:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer

Save the live certificate to a file:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -out example.pem

Look for at the bottom of the output. means the server is not sending its intermediate. shows the same verdict in fewer lines; see the curl cheat sheet.

Generate Keys

is the current all-purpose generator. still works in OpenSSL 3 and is not going anywhere soon. For SSH login keys, use instead, or the SSH key generator.

CommandWhat it does
RSA 4096 private key
Same thing, older syntax
ECDSA P-256 key (smaller, faster, fine for TLS)
Same EC key, older syntax
Ed25519 key
RSA key encrypted with a passphrase
Extract the public key
Print the key details ( and also work)
Verify an RSA key is internally consistent
Remove the passphrase from a key
Add a passphrase to a key
Rewrite a PKCS#8 as for old software

PuTTY cannot read these keys directly. The PEM to PPK converter and PPK to PEM converter go both ways.

Create a CSR

A certificate signing request is what you send to a CA. Set the subject with so it does not prompt, and put every hostname in a SAN, because browsers ignore the CN.

CommandWhat it does
CSR from an existing key
New key and CSR in one go (prompts for the subject)
Read a CSR
Just the subject
Check the CSR signature
Extract the public key

With SANs, which every public CA now requires:

openssl req -new -key key.pem -out req.csr \
  -subj "/CN=example.com/O=Example Ltd/C=US" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"

needs OpenSSL 1.1.1 or newer. On older builds, put the SANs in a config file and pass .

Self-Signed Certificate

For local development, internal services, and anything that will never face a public browser. A public hostname needs a certificate from a real CA, which for most people means a free Let's Encrypt one; the certbot cheat sheet covers issuing and renewing those.

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:localhost,IP:127.0.0.1"
CommandWhat it does
Key plus self-signed certificate, no passphrase, one year
Self-sign with a key you already have
Self-sign an existing CSR
Same with an EC key

means "no DES", that is, do not encrypt the key. OpenSSL 3 spells it and still accepts .

Convert Formats

The extension tells you very little. , , , and are usually PEM (text starting with ), and sometimes are binary DER, and and are PKCS#12 bundles. Run before converting.

CommandWhat it does
PEM certificate to DER
DER certificate to PEM
PEM key to DER
DER key to PEM
Key, certificate, and chain to PFX (prompts for an export password)
CRT plus KEY to PFX, no chain
PFX without the password prompt (scripts)
PFX to PEM, key and certificates in one file, key unencrypted
Only the private key from a PFX
Only the server certificate from a PFX
Only the CA chain from a PFX
List what a PFX contains
PEM to PKCS#7 (, what Windows and Tomcat sometimes ask for)
PKCS#7 to PEM
Binary PKCS#7 to PEM
Traditional key to PKCS#8 (Java, some Node libraries)
JKS to PKCS#12, the first half of JKS to PEM

OpenSSL cannot read a Java keystore at all. Convert the to a with from the JDK, as above, then run it through the rows to get PEM. Going back, build a PFX with and import it the same way with .

If OpenSSL 3 refuses an old PFX with or , the file uses RC2 or 40-bit ciphers; add . The same flag on makes a PFX that old Windows Server and Java 8 can import.

openssl pkcs12 -in old.pfx -out cert.pem -nodes -legacy
openssl pkcs12 -export -legacy -out cert.pfx -inkey key.pem -in cert.pem -certfile chain.pem

Verify Chain and Key Match

CommandWhat it does
Verify a certificate against a chain file (root plus intermediates)
Verify with the intermediate supplied separately
Verify against the system trust store
Print the chain it built
RSA modulus hash of the certificate
RSA modulus hash of the key; must match the line above
RSA modulus hash of a CSR
Public key hash of a certificate (works for EC and Ed25519 too)
Public key hash of a key; must match the line above

Check that a full-chain file is in the right order (leaf first, then each issuer):

openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout

Each certificate's should be the next certificate's . To tell whether two files hold the same certificate, compare on each rather than diffing the PEM, since the same certificate can be stored with different line endings.

When or fails, the number in says what went wrong:

CodeMeaning
ok, nothing to fix
Unable to get issuer certificate: the issuer is not in the CA file you passed
Certificate has expired ( is not yet valid, usually a wrong clock)
Self-signed certificate: the leaf signed itself, so nothing vouches for it
Self-signed certificate in chain: the chain ends at a root you do not trust, typical for an internal CA
Unable to get local issuer certificate: nothing in your trust store matches what was sent
Unable to verify the first certificate: the server is not sending its intermediate

Codes 20 and 21 are the two you will actually meet in production, and both are server-side: the fix is to serve the full chain (, not ), not to add anything on the client. The SSL checker reports the same missing-intermediate problem in plain words.

CRL and OCSP

Whether a certificate has been revoked is a separate question from whether it verifies.

CommandWhat it does
The OCSP responder URL the certificate points at
Where to fetch the CRL for this certificate
Ask for a stapled OCSP response and print it
Query the responder directly: , , or
Read a CRL ( for the binary form most CAs publish)
Convert a CRL to PEM so can use it
Verify and check the leaf against a CRL
Check every certificate in the chain against the CRL

Revoking a certificate you issued yourself needs the subcommand and its database, not :

openssl ca -config ca.cnf -revoke server.crt
openssl ca -config ca.cnf -gencrl -out crl.pem

takes the certificate file. If you only have a serial number, the copy is in under the directory your config's names, and lists every serial with its status.

Create a CA

A private CA for internal hostnames, so you can sign as many certificates as you like and install one root on your machines.

# Root CA (10 years)
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650 -nodes \
  -subj "/CN=Example Internal CA"

# CSR for a server, with SANs
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
  -subj "/CN=app.internal" -addext "subjectAltName=DNS:app.internal,DNS:app"

# Sign it, keeping the SANs from the CSR (OpenSSL 3)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out server.crt -days 825 -copy_extensions copy
CommandWhat it does
Sign a CSR with your CA
Carry the SANs from the CSR into the certificate (OpenSSL 3; on 1.1.1 use )
Confirm the result chains to your CA

Trust the root on a client: on Debian and Ubuntu, on Fedora and RHEL, and Keychain Access on macOS.

Hash, Encrypt, Random

CommandWhat it does
SHA-256 of a file (, , also work)
HMAC-SHA256
Sign a file with a private key
Verify a signature with the public key
Encrypt a file with a passphrase
Decrypt it (same flags plus )
Base64 encode ( decodes; for a single line)
Base64 a string
Random 32 bytes as base64 (session secrets, API keys)
Random 16 bytes as hex
Random 32 bytes as hex, the usual shape of a JWT or app signing secret
SHA-512 crypt hash for ( for SHA-256, for htpasswd)

Always pass to . Without it OpenSSL derives the key with a single MD5 round and warns you about it. Files encrypted without must be decrypted without it too.

covers one user at a time. For a whole file, the htpasswd generator writes the lines for you.

Ciphers and Providers

CommandWhat it does
Cipher suites this build will offer, with protocol and key exchange
Just the TLS 1.3 suites ( for the older list)
Expand a cipher string before pasting it into nginx or Apache
Offer one suite only, to see whether a server accepts it
Ciphers this build supports (, )
Which providers are loaded: , ,
Benchmark this build (, also work)

OpenSSL 3 moved the old algorithms (RC2, RC4, DES, MD2, and the ciphers inside old PFX files) into a separate provider that is not loaded by default. That is what , , and mean. Load it for one command:

openssl <subcommand> -provider legacy -provider default ...

confirms whether it loaded. on is a shorthand for the same thing.

Install and Version

CommandWhat it does
Which version you have ( adds build flags and paths)
Where it looks for
Every subcommand this build has ( for one subcommand's flags)
Which binary is running ( in cmd, in PowerShell)
Debian and Ubuntu
Fedora, RHEL, and Rocky
macOS, since Apple ships LibreSSL under the same name
Windows ( works too; if the id has moved)

Windows has no openssl.exe of its own, so usually means PATH, not a missing install. Git for Windows already ships one at , which works as-is inside Git Bash. On macOS, printing means you are on Apple's build, which is missing and, in older releases, ; Homebrew installs the real thing but leaves it off PATH, so call or add that directory yourself.

Gotchas

  • Forget and prints the fields you asked for and then the whole PEM block again. Harmless, but it fills the screen.
  • without gets the server's default certificate, which on a shared host is a different site's. Without it sits there waiting for you to type an HTTP request; or ends it. In PowerShell, use since does not exist there.
  • The modulus trick only compares RSA keys. For EC and Ed25519, hash the output instead, as shown above.
  • Chrome, Firefox, and Safari ignore the CN entirely. A certificate without a matching the hostname is rejected even when the CN is right, so always pass .
  • OpenSSL 3 changed a few defaults: uses AES-256 and SHA-256 (old Windows and Java need ), reading an old PFX needs , is now , and is fine but is the documented path. tells you which you have.
  • comes up on Windows when the binary looks where it was compiled to look. prints that path; point at the your install actually shipped, for example . Only and need the config, so other commands work fine without it.
  • is an old 1.0.2 build failing to write into a home directory it cannot find. Set to a path you can write, or upgrade; OpenSSL 1.1.1 and later do not use that file.
  • , , , and are naming conventions, not formats. When a command says , check and add if the file is binary.
  • A key file from OpenSSL is not a PuTTY , and a is not a PEM. The PEM to PPK and PPK to PEM converters handle both directions.

OpenSSL FAQ

Does Windows come with OpenSSL?

No. Windows has its own TLS stack (Schannel) and ships no openssl.exe, so the first thing most people see is 'openssl' is not recognized as an internal or external command. You may still have a copy: Git for Windows installs one at C:\Program Files\Git\usr\bin\openssl.exe that works as-is inside Git Bash. Otherwise install it with winget install --id ShiningLight.OpenSSL.Light or choco install openssl, then reopen the terminal so PATH refreshes. Two things then behave differently from Linux. PowerShell has no </dev/null, so end an s_client command with echo "" | openssl s_client ... instead. And Git Bash rewrites a -subj "/CN=example.com" argument into a Windows path, so prefix that command with MSYS_NO_PATHCONV=1.

How do I check a certificate's expiration date with OpenSSL?

For a file, openssl x509 -in cert.pem -noout -enddate prints the notAfter line, and -dates prints both ends. For a script, openssl x509 -in cert.pem -noout -checkend 2592000 exits 1 if the certificate expires within 30 days (the number is seconds), so it slots into cron or a health check without parsing dates. For a live site, pipe s_client into the same command: openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -enddate.

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

Compare the public key each one carries. The classic way is to hash the RSA modulus of both: openssl x509 -noout -modulus -in cert.pem | openssl md5 and openssl rsa -noout -modulus -in key.pem | openssl md5 should print the same hash. That only works for RSA. The version that works for RSA, EC, and Ed25519 keys alike is openssl x509 -in cert.pem -noout -pubkey | openssl sha256 against openssl pkey -in key.pem -pubout | openssl sha256. Swap x509 for req -in req.csr to check a CSR the same way. If the hashes differ you have the wrong key, and the server will refuse to start or IIS will reject the import.

How do I convert a PFX to PEM?

A PFX (PKCS#12) is one password-protected file holding the private key, the certificate, and the chain, which is what Windows, IIS, and Exchange hand you. openssl pkcs12 -in cert.pfx -out cert.pem -nodes writes all of it to a single PEM, but Linux servers usually want the parts separately: add -nocerts for just the key, -clcerts -nokeys for just the server certificate, and -cacerts -nokeys for just the chain. The output carries Bag Attributes and subject lines above each block; nginx and Apache ignore them, so there is no need to strip them. Going the other way is openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -certfile chain.pem. If OpenSSL 3 refuses an old PFX, or the PFX it writes will not import on old Windows or Java 8, add -legacy at either end.

How do I decrypt a private key with OpenSSL?

People asking this almost always mean removing the passphrase, not decrypting ciphertext. openssl pkey -in key.pem -out key-nopass.pem prompts for the current passphrase and writes the same key without one (openssl rsa -in key.pem -out key-nopass.pem does it for RSA keys). Nginx and Apache cannot start unattended with an encrypted key, which is why people do this, so treat the result like a password and keep it at chmod 600. The prompt that says Enter PEM pass phrase is asking for that passphrase, not for a new one. To go the other way, openssl pkey -in key.pem -aes256 -out key-enc.pem adds one, and generating a key with -nodes (or -noenc on OpenSSL 3) means it never asks in the first place.

Related cheat sheets