⌨️ Daily Life Commands — the SSL cheat sheet

Updated 28 August 2026

The commands worth knowing by heart, from all fourteen modules.

Not everything the track covered — the ones you will actually reach for. Grouped by what you are trying to do, because that is how you will look for them: not "what does x509 do" but "how do I find out when this expires".

⭐ marks the genuinely daily ones. If you learn only those, you will handle most of what comes up.


🔍 Look at something

A certificate on disk

bash
openssl x509 -in cert.pem -noout -text                      # ⭐ everything, readable
openssl x509 -in cert.pem -noout -subject -issuer -dates    # ⭐ the four facts you usually want
openssl x509 -in cert.pem -noout -enddate                   # ⭐ just: when does this die?
openssl x509 -in cert.pem -noout -ext subjectAltName        # ⭐ which names is it valid for?
openssl x509 -in cert.pem -noout -serial -fingerprint -sha256
openssl x509 -in cert.pem -noout -purpose                   #    what is it allowed to do?
openssl x509 -in cert.pem -noout -subject -nameopt RFC2253  #    the DN as a proxy will see it

The other file types

bash
openssl req  -in request.csr -noout -text                   # ⭐ read a CSR
openssl crl  -in ca.crl      -noout -text                   #    read a CRL
openssl crl  -in ca.crl      -noout -lastupdate -nextupdate # ⭐ is this CRL still fresh?
openssl pkey -in key.pem     -noout -text                   #    read a private key
openssl pkcs12 -in bundle.p12 -info -noout                  #    what is inside a .p12?
openssl asn1parse -in cert.pem                              #    the raw ASN.1, when desperate

A live server

bash
echo | openssl s_client -connect host:443 -servername host -verify_hostname host   # ⭐ the full check

echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates             # ⭐ the cert it actually served

echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
  | sed -n '/Certificate chain/,/^---/p'                    # ⭐ what did it send, in order?

echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
  | grep -m1 "Verify return code"                           # ⭐ the numbered verdict

echo | openssl s_client -connect host:443 -servername host -showcerts 2>/dev/null
-servername is not -verify_hostname. The first sets SNI so you get the right certificate. The second actually checks the name. Without it, s_client reports 0 (ok) for a certificate that covers a completely different hostname — which is the most common way people verify the wrong thing and believe it.

🔨 Make something

Keys

bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem       # ⭐ RSA
openssl genpkey -algorithm EC  -pkeyopt ec_paramgen_curve:P-256 -out key.pem    # ⭐ ECDSA, smaller+faster
openssl pkey -in key.pem -pubout -out pub.pem                                   #    just the public half

A CSR

bash
openssl req -new -key key.pem -out req.csr -subj "/CN=example.com"              # ⭐

# with SANs, in one command, no config file
openssl req -new -key key.pem -out req.csr -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"                  # ⭐

A self-signed certificate, for a quick test

bash
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem \
  -days 90 -subj "/CN=test.local" -addext "subjectAltName=DNS:test.local"       # ⭐ one command

A CA, and issuing from it

bash
# the CA
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ca.key
openssl req -x509 -new -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=My CA" \
  -addext "basicConstraints=critical,CA:TRUE" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"                               # ⭐

# the extensions the certificate will get  (a CSR's own extensions are IGNORED)
cat > ext.cnf <<'EOF'
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:example.com
EOF

openssl x509 -req -in req.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 90 -sha256 -extfile ext.cnf -out cert.crt                              # ⭐
The single most forgotten thing on this page: -extfile.

Without it, openssl x509 -req produces a certificate with no SAN, no EKU and no basicConstraints — and openssl verify will still say OK, because it does not check hostnames. It will fail in every browser.

Whatever a CSR asks for, the issuer decides the extensions. That is deliberate: otherwise anyone could request CA:TRUE.


✅ Check that something is right

bash
openssl verify -CAfile ca.crt cert.crt                      # ⭐ does the chain verify?
openssl verify -CAfile root.crt -untrusted int.crt cert.crt # ⭐ with an intermediate
openssl verify -CAfile ca.crt -purpose sslclient cert.crt   #    valid for CLIENT auth?
openssl verify -CApath ./mystore cert.crt                   #    against a hashed directory

# does this key belong to this certificate?  compare the two hashes
openssl x509 -in cert.crt -noout -pubkey | openssl sha256    # ⭐
openssl pkey -in key.pem  -pubout        | openssl sha256    # ⭐

# did a rotation actually rotate the KEY, or only reissue the certificate?
openssl x509 -in old.crt -noout -pubkey | openssl sha256     # ⭐ same hash = key NOT rotated
openssl x509 -in new.crt -noout -pubkey | openssl sha256

🔄 Convert between formats

bash
openssl x509 -in cert.der -inform der -out cert.pem                     # ⭐ DER → PEM
openssl x509 -in cert.pem -outform der -out cert.der                    #    PEM → DER
openssl pkcs12 -export -out bundle.p12 -inkey key.pem -in cert.pem \
  -certfile chain.pem                                                   # ⭐ → PKCS#12 (Java, Windows)
openssl pkcs12 -in bundle.p12 -nokeys  -out certs.pem                   #    ← certificates out
openssl pkcs12 -in bundle.p12 -nocerts -nodes -out key.pem              #    ← key out
openssl pkcs8 -topk8 -in key.pem -out key-pkcs8.pem -nocrypt            #    PKCS#1 → PKCS#8

cat leaf.crt intermediate.crt > fullchain.pem                           # ⭐ leaf FIRST, no root

🚨 Something is broken

The four-command reflex, cheapest first. Stop as soon as you know the answer.
bash
# 1. what is broken?  ten seconds, right most of the time
curl -sS https://host/ 2>&1 | head -1

# 2. the verdict and the chain together
echo | openssl s_client -connect host:443 -servername host -verify_hostname host 2>/dev/null \
  | grep -E "Verify return code|^ [0-9] s:|^   i:"

# 3. the certificate you actually received
echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

# 4. what does the SERVER's log say?  the client only ever sees a summary
tail -50 /var/log/nginx/error.log | grep -i ssl

The verify codes, and what each one means for you

CodeMeaningWhat to do
0okNot a certificate problem. Look elsewhere
10certificate has expiredCheck the whole chain — it may be the intermediate
18self-signed certificateA test certificate reached a real environment
19self-signed certificate in chainThe server is sending the root. Strip it from fullchain.pem
20unable to get local issuer certificateYour trust store. Missing internal root, or no ca-certificates
21unable to verify the first certificateTheir server. It did not send the intermediate
23certificate revokedIt is on the CRL
26unsuitable certificate purposeWrong EKU — a serverAuth certificate used as a client
34unhandled critical extensionOften a CT precertificate. Not meant to be used
47permitted subtree violationA name constraint refused that name
62hostname mismatchNo SAN covers the name you asked for

Decoding the other kind of error number

bash
openssl errstr 05800074      # ⭐ error:05800074:x509 routines::key values mismatch
openssl errstr 0A000086      #    error:0A000086:SSL routines::certificate verify failed

🌐 DNS, CAA and Certificate Transparency

bash
dig +short CAA example.com                                  # ⭐ who may issue for this domain?
dig CAA example.com +noall +answer                          # ⭐ with TTLs, for change work
dig +short CAA example.com @1.1.1.1                         #    from a specific resolver
dig +short CAA example.com @ns1.example.com                 # ⭐ straight from the authoritative server

# every certificate ever issued for a domain, from the CT logs
curl -s 'https://crt.sh/?q=%25.example.com&output=json&exclude=expired' \
  | python3 -c 'import json,sys; [print(n) for r in json.load(sys.stdin)
      for n in r["name_value"].splitlines()]' | sort -u     # ⭐

openssl x509 -in cert.pem -noout -ext ct_precert_scts       # ⭐ the SCTs, if any
Zero SCTs on a major public site is a near-definitive TLS-interception signal. A proxy re-issuing certificates locally cannot fake them, because no public log will sign for it. One command, and it is faster than reasoning about an issuer you do not recognise.

🖥️ Servers

nginx

bash
nginx -t -c /path/nginx.conf                # ⭐ ALWAYS test before reloading
nginx -s reload -c /path/nginx.conf         # ⭐ pick up new certificates, no dropped connections
nginx -s stop -c /path/nginx.conf
grep -rn "mycert" /etc/nginx/               # ⭐ EVERY place a file is referenced
plain text
ssl_certificate      /path/fullchain.pem;   # leaf + intermediates, never the root
ssl_certificate_key  /path/privkey.pem;
ssl_client_certificate /path/client-ca.crt; # mTLS: who may sign client certificates
ssl_verify_client    on;                    # DEFAULT IS off
ssl_verify_depth     2;                     # DEFAULT IS 1   (Apache defaults to 10)
ssl_crl              /path/client-ca.crl;   # re-read only on reload

Apache

bash
apachectl configtest                        # ⭐ the equivalent of nginx -t
apachectl graceful                          # ⭐ reload without dropping connections

A throwaway TLS server, for testing

bash
openssl s_server -accept 4433 -cert cert.pem -key key.pem -www          # ⭐
openssl s_server -accept 4433 -cert cert.pem -key key.pem \
  -CAfile client-ca.crt -Verify 1 -verify_return_error -www             # ⭐ mTLS
-verify_return_error is not optional. Without it, s_server prints the verification error and completes the handshake anyway — so your mTLS lab appears to work while accepting certificates from any CA at all.

🤝 Client certificates and mTLS

bash
curl --cacert server-ca.crt --cert client.crt --key client.key https://host/    # ⭐
cat client.crt client-int.crt > client-chain.crt                                # ⭐ clients send chains too
openssl verify -CAfile client-ca.crt -purpose sslclient client.crt              # ⭐ would a server accept it?
openssl x509 -in client.crt -noout -subject -nameopt RFC2253                    # ⭐ the string nginx will see

echo | openssl s_client -connect host:443 2>&1 \
  | sed -n '/Acceptable client certificate CA names/,/Peer signing/p'           # ⭐ which CA does it want?

📅 Expiry and renewal

bash
# days left on a live endpoint
end=$(echo | openssl s_client -connect host:443 -servername host 2>/dev/null \
      | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 )) days               # ⭐

certbot certificates                        # ⭐ what certbot manages, and when each expires
certbot renew --dry-run                     # ⭐ will renewal work? run this BEFORE it matters
certbot renew --force-renewal               #    renew now regardless
systemctl list-timers | grep certbot        #    is the renewal timer actually running?
Read expiry from the connection, never from the file. A file tells you what was deployed. Connecting tells you what is being served, and those differ far more often than anyone expects — a replaced file is not live until the server reloads, and a rejected reload leaves the old certificate serving with no sign of failure.

🔎 Scanners

bash
sslscan --no-colour --sni-name=host host:443             # ⭐ what WOULD it accept?
testssl.sh -S --add-ca internal-root.crt host:443        # ⭐ certificate and chain, fast
testssl.sh -U host:443                                   #    every named vulnerability
testssl.sh --ip 10.0.0.7 www.example.com:443             # ⭐ audit ONE node behind a load balancer
testssl.sh --add-ca internal-root.crt host:443 | tail -20 # ⭐ an SSL Labs-style grade, locally
Only scan what you are responsible for. A scan is thousands of probing connections. Against someone else's infrastructure it is unauthorised scanning, indistinguishable from an attack in their logs, and unlawful in many places regardless of intent.

🧹 Clean up the lab

bash
pgrep -f 'openssl s_server' | xargs -r kill    # stop any test servers
nginx -s stop -c ~/tls-lab/*/nginx.conf 2>/dev/null
rm -rf ~/tls-lab                               # remove every file this track created

If you remember only five lines from this entire page, make them these.
bash
curl -sS https://host/ 2>&1 | head -1                                    # what is broken?
echo | openssl s_client -connect host:443 -servername host -verify_hostname host
openssl x509 -in cert.pem -noout -subject -issuer -dates -ext subjectAltName
openssl x509 -in cert.pem -noout -pubkey | openssl sha256               # does the key match?
nginx -t && nginx -s reload                                             # test, THEN reload

They cover triage, inspection, verification and safe change — which is most of the job.

Spotted a mistake or want something added? Send me a note.