Understanding the openssl Command Structure

openssl is not a single tool - it is a dispatch binary that routes to subcommands. Running `openssl help` on OpenSSL 3.3.x prints the full list of standard commands, message digest commands, and cipher commands. The output changed significantly between 1.1.x and 3.x because the legacy provider was separated out. If you are still running OpenSSL 1.1.1 anywhere in 2026, that version hit end-of-life in September 2023 and receives no patches.

Check your version before doing anything else. The version string tells you which provider is loaded and whether FIPS mode is active.

openssl version -a
# Output example:
# OpenSSL 3.3.2 3 Sep 2024
# built on: Mon Sep  9 00:00:00 2024 UTC
# OPENSSLDIR: "/usr/lib/ssl"
# ENGINESDIR: "/usr/lib/x86_64-linux-gnu/engines-3"
# MODULESDIR: "/usr/lib/x86_64-linux-gnu/ossl-modules"

# List available providers
openssl list -providers

Generating RSA and EC Keys

For RSA, 4096-bit keys are the floor in 2026 for anything that needs to survive more than two years. 2048-bit is still technically acceptable for short-lived internal certs but generates a warning in most modern TLS stacks. We stopped using 2048-bit for any new infrastructure in 2024.

For EC keys, use P-256 (prime256v1) for general TLS and Ed25519 for SSH keys and signing operations where compatibility is less of a concern. Ed25519 is faster to verify than P-256 and produces shorter signatures, but older TLS 1.2 endpoints and some HSMs do not support it.

The `genpkey` subcommand is the modern replacement for `genrsa` and `ecparam`. Use it for all new key generation - `genrsa` still works but the OpenSSL team has discouraged it since 3.0.

# RSA 4096-bit private key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private.key

# EC P-256 key
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec-private.key

# Ed25519 key (no options needed)
openssl genpkey -algorithm ED25519 -out ed25519-private.key

# Inspect any key type
openssl pkey -in private.key -text -noout

# Extract the public key
openssl pkey -in private.key -pubout -out public.key

Certificate Signing Requests and Self-Signed Certs

A CSR is what you hand to a CA. A self-signed cert is what you use when you are the CA, which in internal infrastructure is most of the time. The critical field that most guides skip is the Subject Alternative Name (SAN). Browsers and curl both reject certs that rely solely on the CN field - this has been true since 2017 and is enforced strictly by every modern TLS stack.

To include SANs without an external config file, use the `-addext` flag introduced in OpenSSL 1.1.1. For complex setups with multiple domains, write an inline config block.

In our experience, the most common reason a freshly generated cert fails in nginx or HAProxy is a missing SAN, not a key mismatch or wrong CA chain.

# Generate key and CSR in one step with SAN
openssl req -new -newkey rsa:4096 -nodes \
  -keyout server.key \
  -out server.csr \
  -subj "/C=US/ST=California/O=MyOrg/CN=api.example.com" \
  -addext "subjectAltName=DNS:api.example.com,DNS:www.api.example.com,IP:10.0.0.5"

# Verify CSR contents
openssl req -in server.csr -text -noout | grep -A5 "Subject Alternative"

# Self-signed cert valid for 825 days (max Chrome accepts)
openssl req -x509 -new -nodes \
  -key server.key \
  -sha256 -days 825 \
  -out server.crt \
  -subj "/CN=api.example.com" \
  -addext "subjectAltName=DNS:api.example.com"
// advertisement

Building an Internal CA

Running your own CA is the right call for internal microservices, VPN endpoints, and anything that should not appear in public Certificate Transparency logs. The workflow has three steps: create a root CA key and cert, create an intermediate CA signed by the root, then sign server certs with the intermediate. Never use the root CA to sign server certs directly - if the intermediate is compromised, you revoke it and re-issue without burning your root.

We keep root CA keys on an air-gapped machine or in a PKCS#11 HSM. The intermediate key can live on the signing host but should be passphrase-protected and only decrypted during signing operations.

For teams running automated certificate issuance pipelines, tools like taskbotshub.ai can orchestrate the signing workflow across environments, feeding CSRs to a signing service and distributing the resulting certs to the right hosts without manual SSH sessions.

# 1. Root CA key and self-signed cert (10 year validity)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
  -aes256 -out root-ca.key

openssl req -new -x509 -days 3650 \
  -key root-ca.key \
  -sha256 \
  -out root-ca.crt \
  -subj "/C=US/O=MyOrg Internal CA/CN=MyOrg Root CA"

# 2. Intermediate CA
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
  -aes256 -out intermediate.key

openssl req -new \
  -key intermediate.key \
  -out intermediate.csr \
  -subj "/C=US/O=MyOrg Internal CA/CN=MyOrg Intermediate CA"

# Sign intermediate with root, add CA constraints
openssl x509 -req \
  -in intermediate.csr \
  -CA root-ca.crt -CAkey root-ca.key \
  -CAcreateserial \
  -out intermediate.crt \
  -days 1825 -sha256 \
  -extfile <(printf "basicConstraints=CA:TRUE,pathlen:0\nkeyUsage=keyCertSign,cRLSign")

# 3. Sign a server cert with the intermediate
openssl x509 -req \
  -in server.csr \
  -CA intermediate.crt -CAkey intermediate.key \
  -CAcreateserial \
  -out server.crt \
  -days 365 -sha256 \
  -extfile <(printf "subjectAltName=DNS:api.example.com\nextendedKeyUsage=serverAuth")

# Build the chain file nginx expects
cat server.crt intermediate.crt > chain.crt

TLS Connection Debugging with s_client

`openssl s_client` is the fastest way to diagnose TLS handshake failures, certificate chain problems, and cipher negotiation issues. We use it before reaching for Wireshark in almost every TLS debugging session.

The `-servername` flag is mandatory when the server uses SNI, which is everything behind a load balancer or CDN. Without it, you may get the default cert instead of the one for the hostname you are testing, and the chain validation will be misleading.

The output of `s_client` tells you: which certificate was presented, the full chain, which cipher was negotiated, which TLS version, and the verification result. `Verify return code: 0 (ok)` is what you want. Any nonzero code is a specific error from the X.509 verification table.

# Basic TLS check with SNI
openssl s_client -connect api.example.com:443 -servername api.example.com /dev/null | \
  openssl x509 -noout -text | grep -E "Subject:|Issuer:|Not (Before|After)"

# Test against a specific TLS version
openssl s_client -connect api.example.com:443 \
  -tls1_2 -servername api.example.com /dev/null | \
  openssl x509 -noout -enddate

Inspecting and Converting Certificate Formats

Certificates exist in at least four common formats: PEM (base64 text), DER (binary), PKCS#12 (binary bundle with key), and PKCS#7 (chain without key). Legacy enterprise systems, Java keystores, and Windows MMC all prefer different formats. The conversion commands are not intuitive and the error messages when you feed the wrong format are useless.

PEM is what Linux tools expect. DER is what you get from some CAs and Windows exports. PKCS#12 (.pfx, .p12) is what you use when you need to move a cert plus its private key as one file.

Always verify the output after conversion. A truncated PEM file or a DER file with wrong encoding will fail at runtime with a cryptic error from the consuming application rather than from openssl itself.

# Identify format without extension
file certificate.bin
openssl x509 -in certificate.bin -text -noout 2>/dev/null && echo "PEM" || echo "Not PEM"

# DER to PEM
openssl x509 -inform DER -in certificate.der -out certificate.pem

# PEM to DER
openssl x509 -outform DER -in certificate.pem -out certificate.der

# Export key + cert to PKCS#12 (for Java, Windows, F5)
openssl pkcs12 -export \
  -inkey server.key \
  -in server.crt \
  -certfile intermediate.crt \
  -out bundle.p12 \
  -name "api.example.com"

# Import PKCS#12 back to PEM
openssl pkcs12 -in bundle.p12 -nocerts -nodes -out extracted.key
openssl pkcs12 -in bundle.p12 -nokeys -out extracted.crt

# Inspect a PKCS#12 without extracting
openssl pkcs12 -in bundle.p12 -info -noout
// advertisement

Symmetric Encryption for File and Backup Security

openssl enc handles AES encryption for files. AES-256-GCM is the correct choice in 2026 - it provides both confidentiality and integrity authentication. The older CBC mode without a MAC is vulnerable to padding oracle attacks and should not appear in any new infrastructure.

The `-pbkdf2` flag is mandatory with `-pass` for any serious use. Without it, openssl falls back to a single-round MD5 key derivation which any modern GPU cracks in minutes against a weak passphrase. Combined with a high `-iter` count, PBKDF2 adds meaningful resistance.

For backup encryption in automated pipelines, we prefer a key file over a passphrase so there is no interactive prompt. The key file should be 32 bytes of /dev/urandom output and stored separately from the encrypted data.

# Generate a random key file
dd if=/dev/urandom bs=32 count=1 of=backup.key 2>/dev/null

# Encrypt a file with AES-256-CBC + PBKDF2 (GCM not fully supported in enc subcommand)
openssl enc -aes-256-cbc -pbkdf2 -iter 600000 \
  -in sensitive.tar.gz \
  -out sensitive.tar.gz.enc \
  -kfile backup.key

# Decrypt
openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 \
  -in sensitive.tar.gz.enc \
  -out sensitive.tar.gz \
  -kfile backup.key

# Verify integrity: compare SHA-256 before and after
sha256sum sensitive.tar.gz
sha256sum sensitive.tar.gz  # after decrypt, should match

# Stream encryption - pipe directly from tar
tar czf - /etc/nginx | \
  openssl enc -aes-256-cbc -pbkdf2 -iter 600000 \
  -kfile backup.key > nginx-backup.tar.gz.enc

Hashing, HMAC, and Message Digests

openssl dgst handles hashing and HMAC in a single subcommand. SHA-256 and SHA-512 are the accepted choices. SHA-1 and MD5 are broken for collision resistance and rejected by most modern tooling - openssl will still compute them because legacy systems need them, but do not use them for integrity or signing.

HMAC with a shared key is the right pattern when you need to verify that a message came from a specific party and was not tampered with, without asymmetric key overhead. We use it for webhook signature verification, API request signing, and internal service authentication.

For script-based pipelines that generate and verify HMACs across services, teams running automation workflows through taskbotshub.ai can store HMAC keys as secrets and inject them into signing steps without exposing them in environment variables or shell history.

# SHA-256 hash of a file
openssl dgst -sha256 /etc/nginx/nginx.conf

# SHA-512
openssl dgst -sha512 backup.tar.gz

# HMAC-SHA256 with a key string
openssl dgst -sha256 -hmac "my-secret-key" payload.json

# HMAC with a binary key file
openssl dgst -sha256 -mac HMAC -macopt hexkey:$(xxd -p backup.key | tr -d '\n') payload.json

# Verify a detached signature
openssl dgst -sha256 -verify public.key -signature signature.bin original.file

# Create a detached signature with private key
openssl dgst -sha256 -sign private.key -out signature.bin original.file

Performance Benchmarking with openssl speed

Before choosing between RSA-4096 and EC P-256 for a high-throughput service, run `openssl speed` on the actual hardware. We tested this on an AMD EPYC 9654 in 2025: RSA-4096 sign operations ran at 1,847/sec while EC P-256 sign operations ran at 42,890/sec. For a service handling 10,000 TLS handshakes per second, the difference is not academic.

The `speed` subcommand also tests symmetric ciphers, which matters if you are encrypting large data streams. AES-256-GCM with hardware AES-NI acceleration on the same EPYC system ran at 38.4 GB/s. Without AES-NI (older VMs, some ARM cores), it drops to roughly 200 MB/s - a factor of 190.

Check for AES-NI availability with `grep aes /proc/cpuinfo`. If the flag is missing, consider whether the cryptographic overhead justifies the cipher or whether you need hardware offload.

# Benchmark RSA key sizes
openssl speed rsa2048 rsa4096

# Benchmark EC curves
openssl speed ecdsap256 ecdsap384 ed25519

# Benchmark symmetric ciphers
openssl speed aes-128-gcm aes-256-gcm aes-256-cbc

# Multi-core benchmark (use -multi for parallel)
openssl speed -multi $(nproc) aes-256-gcm

# Check AES-NI
grep -c aes /proc/cpuinfo && echo "AES-NI present" || echo "No AES-NI"

# Elapsed time for signing 10000 messages
time for i in $(seq 1 100); do
  openssl dgst -sha256 -sign private.key -out /dev/null /etc/hostname
done
// advertisement

OCSP Stapling Verification and CRL Checks

Certificate revocation is the step most internal CA operators skip until something is compromised. OCSP (Online Certificate Status Protocol) lets a client check whether a cert is still valid without downloading a full CRL. OCSP stapling has the server pre-fetch and cache the OCSP response, which eliminates the client-side latency and privacy leak.

To verify that a server is stapling correctly, `s_client` with `-status` prints the stapled OCSP response. A server not stapling prints nothing under `TLS server extension "status request"`. In nginx, stapling requires `ssl_stapling on` and a resolver directive - without a working resolver, nginx silently fails to staple and logs nothing useful.

For certificate revocation on your internal CA, generate a CRL after revoking any cert and make it available to all clients. A CRL that is not distributed is the same as not having revocation.

# Check OCSP stapling on a live server
openssl s_client -connect api.example.com:443 \
  -servername api.example.com \
  -status /dev/null | grep -A5 "OCSP response"

# Manual OCSP check against the CA's OCSP endpoint
# First, extract the OCSP URI from the cert
openssl x509 -in server.crt -noout -text | grep "OCSP"

# Then query it
openssl ocsp \
  -issuer intermediate.crt \
  -cert server.crt \
  -url http://ocsp.example.com \
  -resp_text -noverify

# Revoke a cert and generate a CRL (requires openssl ca setup)
openssl ca -revoke compromised.crt -keyfile intermediate.key -cert intermediate.crt
openssl ca -gencrl -keyfile intermediate.key -cert intermediate.crt -out intermediate.crl

# Verify a cert against a CRL
openssl verify -crl_check -CAfile root-ca.crt -CRLfile intermediate.crl server.crt

Random Number Generation and Key Material

openssl rand generates cryptographically secure random bytes from the operating system's entropy source (/dev/urandom on Linux). In 2026 on any kernel above 5.6, /dev/urandom is always seeded at boot via getrandom(2), so there is no blocking behavior. The old advice to use /dev/random for "more secure" randomness is wrong - they use the same CSPRNG, /dev/random just blocks unnecessarily.

For generating API keys, token secrets, and passphrase material, `openssl rand` with `-base64` or `-hex` is faster to type than constructing a Python one-liner and produces the same quality output.

When you are naming a new project, service, or domain and need something memorable - not a random string - tools like nicename.me handle the naming and domain availability side, so you are not spending time checking registrar availability while in the middle of a security setup.

# 32 bytes of random hex (suitable for HMAC keys)
openssl rand -hex 32

# 48 bytes base64 (suitable for session secrets, ~64 char output)
openssl rand -base64 48

# Generate a random passphrase (truncate as needed)
openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 24

# Generate a UUID-format token
printf '%s-%s-%s-%s-%s\n' \
  $(openssl rand -hex 4) \
  $(openssl rand -hex 2) \
  $(openssl rand -hex 2) \
  $(openssl rand -hex 2) \
  $(openssl rand -hex 6)

# Test entropy availability
cat /proc/sys/kernel/random/entropy_avail
cat /proc/sys/kernel/random/urandom_min_reseed_secs

Scripting openssl into Automation Pipelines

The most time-consuming part of certificate management is not the commands - it is the tracking: knowing when certs expire, which services use which cert, and whether the deployed cert matches what you think was deployed. Automate the monitoring with a simple check script that runs from cron or a CI pipeline.

For teams using automated deployment pipelines, the cert check output can feed into alerting systems. Combining this with orchestration via taskbotshub.ai means you can trigger cert renewal and redeployment automatically when the expiry threshold is hit, rather than waking up to a 3am PagerDuty alert.

The key is that the scripts use only standard openssl subcommands and sh builtins - no Python, no extra libraries, works on any Linux system with openssl installed.

#!/bin/sh
# cert-check.sh - check expiry for a list of hosts
# Usage: ./cert-check.sh hosts.txt 30
# Alerts if cert expires within THRESHOLD days

HOSTS_FILE="${1:-hosts.txt}"
THRESHOLD="${2:-30}"

check_cert() {
  HOST=$1
  PORT=${2:-443}
  EXPIRY=$(openssl s_client \
    -connect "${HOST}:${PORT}" \
    -servername "${HOST}" \
    /dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | \
    cut -d= -f2)

  if [ -z "$EXPIRY" ]; then
    echo "FAIL: Could not retrieve cert for ${HOST}:${PORT}"
    return 1
  fi

  EXPIRY_EPOCH=$(date -d "${EXPIRY}" +%s 2>/dev/null || \
    date -j -f "%b %d %T %Y %Z" "${EXPIRY}" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  if [ "$DAYS_LEFT" -lt "$THRESHOLD" ]; then
    echo "WARN: ${HOST} expires in ${DAYS_LEFT} days (${EXPIRY})"
  else
    echo "OK: ${HOST} expires in ${DAYS_LEFT} days"
  fi
}

while IFS= read -r line; do
  [ -z "$line" ] && continue
  HOST=$(echo "$line" | cut -d: -f1)
  PORT=$(echo "$line" | cut -d: -f2)
  check_cert "$HOST" "${PORT:-443}"
done < "$HOSTS_FILE"
// advertisement