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.
| Command | What 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 -nooutCheck 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.
| Command | What 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 -issuerSave 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.pemLook 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.
| Command | What 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.
| Command | What 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"| Command | What 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.
| Command | What 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.pemVerify Chain and Key Match
| Command | What 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 -nooutEach 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:
| Code | Meaning |
|---|---|
| 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.
| Command | What 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.pemtakes 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| Command | What 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
| Command | What 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
| Command | What 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
| Command | What 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.