Guides

openssl commands: checking certificates, generating keys and random values

openssl s_client to check a live server's TLS, x509 to read a certificate file, req to generate a CSR or self-signed certificate, and rand for secrets — with real output.

Published ~7 min read

Short answer: openssl s_client -connect HOST:443 -servername HOST -brief checks a live TLS connection from the outside, and openssl x509 -in cert.pem -noout -text reads a certificate file you already have. Everything below was run against OpenSSL 3.0.22 on Debian 12; a few flags differ on OpenSSL 1.1.1 (Ubuntu 20.04 and Debian 11), noted where it matters.

TaskCommand
Check a live server's certificateopenssl s_client -connect example.com:443 -servername example.com -brief </dev/null
Expiry date of a .pem fileopenssl x509 -in cert.pem -noout -dates
Full details of a .pem fileopenssl x509 -in cert.pem -noout -text
Generate a self-signed certopenssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
Generate a CSR for a CAopenssl req -new -newkey rsa:2048 -keyout key.pem -out request.csr -nodes
Random hex stringopenssl rand -hex 32
Random base64 stringopenssl rand -base64 32
SHA-256 of a fileopenssl dgst -sha256 file.iso
Decode a base64 blobopenssl base64 -d -in file.b64 -out file.bin
Check a private key matches a certopenssl x509 -noout -modulus -in cert.pem | openssl md5
List supported TLS ciphersopenssl ciphers -v
OpenSSL versionopenssl version

Checking a live server: openssl s_client

Debian · Ubuntu · macOS
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null
Example output
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = example.com
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Server Temp Key: X25519, 253 bits
DONE
  • -servername sends SNI, which almost every host needs to pick the right certificate when several sites share one IP; leaving it out can connect you to the server’s default certificate instead of the one you meant to check.
  • -brief is the short summary above; without it, s_client prints the entire certificate chain and the handshake in full, useful when -brief is not enough.
  • </dev/null closes standard input right away, so the command exits on its own instead of waiting at an open connection where you could otherwise type raw HTTP by hand.

-brief needs OpenSSL 1.1.1 or newer; every current distribution has it. The error line it prints when something is actually wrong, and what each one means, is covered in SSL handshake failed. The SSL checker runs the same check from a browser, with the full chain and expiry date laid out, when a terminal is not handy.

Other than HTTPS: SMTP, IMAP and STARTTLS

a server that upgrades to TLS instead of starting with it
openssl s_client -connect mail.example.com:587 -starttls smtp
openssl s_client -connect mail.example.com:993

Port 993 (IMAPS) is TLS from the first byte, same as 443; port 587 (SMTP submission) starts in plain text and only switches to TLS after a STARTTLS command, which is what -starttls smtp sends for you. Using the wrong one against the wrong port either hangs or fails at once. IMAP and POP3 ports lists which ones expect which mode.

Reading a certificate file: openssl x509

expiry date only
openssl x509 -in cert.pem -noout -dates
Example output
notBefore=Oct 14 09:00:00 2026 GMT
notAfter=Jan 12 09:00:00 2027 GMT
everything: issuer, subject, SANs, validity, fingerprint
openssl x509 -in cert.pem -noout -text

-noout stops it printing the raw base64 PEM block again underneath, which you almost never want to look at. The names a certificate actually covers are the Subject Alternative Names, not the subject line, which browsers stopped trusting on its own years ago:

Linux
openssl x509 -in cert.pem -noout -ext subjectAltName
Example output
X509v3 Subject Alternative Name:
    DNS:example.com, DNS:www.example.com

Fingerprint and serial

Linux
openssl x509 -in cert.pem -noout -fingerprint -sha256
openssl x509 -in cert.pem -noout -serial

Creating a CSR and a self-signed certificate

A certificate authority needs a Certificate Signing Request, not a finished certificate; a self-signed one is for internal tools and local development, since browsers reject it without you adding it as trusted yourself.

CSR for a CA
openssl req -new -newkey rsa:2048 -keyout key.pem -out request.csr -nodes \
  -subj "/CN=example.com"
self-signed certificate, one command
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
  • -nodes (“no DES”) writes the private key without a passphrase — needed for a service that starts unattended, since otherwise it would ask for the password on every start.
  • -newkey rsa:2048 generates a new key in the same step; use ec -pkeyopt ec_paramgen_curve:prime256v1 instead of rsa:2048 for a smaller, faster Ed25519-era alternative where the client side supports it.
  • -addext "subjectAltName=…" is the part people forget: without it, modern browsers and curl reject the certificate even though the subject line looks right, because there is no SAN at all.

For a real public site, Certbot and Let’s Encrypt do this whole exchange with a real CA for free — see nginx reverse proxy with SSL. openssl by hand is for internal services, a mail server’s own TLS, or testing.

Does this private key match that certificate?

compare the two outputs
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5

Identical output means the key and certificate are a pair. This is the quickest way to find out whether an nginx config is pointing at mismatched files from two different certificate renewals.

openssl rand: random strings for scripts and secrets

Linux
openssl rand -hex 32      # 64 hex characters, often used as an API key or token
openssl rand -base64 32   # shorter, mixed-case, used for passwords and secrets
openssl rand -hex 16 > /etc/myapp/secret.key

Both draw from the same cryptographically secure source as /dev/urandom; the only difference is the encoding. -hex doubles the byte count in its output length (16 bytes become 32 characters), -base64 is about 4/3 the byte count.

Checksums and signatures: openssl dgst

Linux
openssl dgst -sha256 ubuntu-24.04.iso
openssl dgst -sha256 -verify pubkey.pem -signature file.sig file.tar.gz

The first line is the same job as sha256sum, just from openssl directly, useful on a system that has openssl but not the coreutils checksum tools (some minimal containers). The second verifies a detached signature against a public key, which is how you check a download’s signature file rather than only its checksum.

Encoding and decoding: openssl base64

Linux
openssl base64 -in file.bin -out file.b64        # encode
openssl base64 -d -in file.b64 -out file.bin     # decode
echo -n "hello" | openssl base64                 # encode a short string

Plain base64 (the separate coreutils command) does the same job and is on every system too; openssl’s version is handy when you are already piping through openssl for something else in the same line.

What TLS versions and ciphers does this build support?

Linux
openssl version
openssl ciphers -v | head -20

openssl version matters more than it looks: OpenSSL 1.1.1 reached end of life in September 2023 and gets no security fixes anywhere, so a package copied by hand from an old release should be replaced with one linked against OpenSSL 3, or the distribution’s own compatibility package where one exists (Rocky and AlmaLinux 9 ship compat-openssl11 as a stopgap for software that still needs it).

Common openssl error lines

LineMeaning
connect: Connection refusedNothing is listening on that port — wrong port, or the service is down
errno=110 (Connection timed out)No answer at all: a firewall between you and the server, most likely
00…:SSL routines:…:wrong version numberPlain HTTP answered on a port you asked for TLS on — see SSL_ERROR_RX_RECORD_TOO_LONG
Verify return code: 10 (certificate has expired)Exactly what it says; renew it
Verify return code: 20 (unable to get local issuer certificate)The server sent its certificate but not the intermediate — a chain problem, not an expiry
Verify return code: 21 (unable to verify the first certificate)Same chain problem, OpenSSL's older wording for it

The “wrong version number” case is covered end to end, with the matching browser error text, in SSL_ERROR_RX_RECORD_TOO_LONG.

Frequently asked

How do I check a website's SSL certificate from the command line?

openssl s_client -connect example.com:443 -servername example.com -brief </dev/null. -servername sends SNI, which most hosts need to return the right certificate when several sites share one IP; without it you may see the server's default certificate instead of the one you meant to check.

How do I check when a certificate file expires?

openssl x509 -in cert.pem -noout -dates prints notBefore and notAfter. For every other detail — issuer, the Subject Alternative Names a certificate actually covers, the fingerprint — openssl x509 -in cert.pem -noout -text prints the lot.

How do I generate a self-signed certificate with openssl?

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com". The -addext line matters: without a Subject Alternative Name, current browsers and curl reject the certificate even if the subject line looks correct.

How do I generate a random string with openssl for a secret or API key?

openssl rand -hex 32 for a 64-character hex string, or openssl rand -base64 32 for a shorter mixed-case one. Both draw from the same cryptographically secure source; only the encoding differs.

Related articles

All guides