OpenSSL Hands-On
Why This Matters
In the previous chapter, we learned what TLS, certificates, and PKI are. Now it is time to get your hands dirty. OpenSSL is the Swiss Army knife of cryptography on Linux -- it generates keys, creates certificates, tests TLS connections, and converts between formats. You will use it when setting up HTTPS on a web server, debugging certificate problems at 3 AM, creating a development CA for your team, or verifying that a certificate renewal actually worked.
This chapter is almost entirely commands and output. You will generate keys, sign certificates, build a certificate chain, and test it all -- the way you would in a real production environment.
Try This Right Now
# What version of OpenSSL do you have?
openssl version
# With build details
openssl version -a
# Generate a random 32-byte hex string (useful for tokens/passwords)
openssl rand -hex 32
# Quick hash of a file
echo "hello world" | openssl dgst -sha256
These commands confirm OpenSSL is installed and working.
Generating Private Keys
The private key is the foundation of everything. You generate it first, and everything else (CSR, certificate) builds on top of it.
RSA Keys
# Generate a 2048-bit RSA private key
openssl genrsa -out server-rsa.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
....................+++++
.......+++++
e is 65537 (0x010001)
# Generate a 4096-bit RSA key (more secure, slightly slower)
openssl genrsa -out server-rsa4096.key 4096
# Generate an encrypted private key (password-protected)
openssl genrsa -aes256 -out server-encrypted.key 2048
# You will be prompted for a passphrase
WARNING: If you password-protect a key used by a web server, the server will prompt for the passphrase every time it starts. This means automated restarts will hang. For server keys, typically leave them unencrypted but protect them with file permissions.
# Examine the key
openssl rsa -in server-rsa.key -text -noout | head -20
# Extract the public key from the private key
openssl rsa -in server-rsa.key -pubout -out server-rsa.pub
# View the public key
cat server-rsa.pub
ECDSA Keys (Recommended for New Deployments)
ECDSA keys are smaller and faster than RSA while providing equivalent security.
# List available elliptic curves
openssl ecparam -list_curves | head -10
# Generate an ECDSA key using the P-256 curve (most common)
openssl ecparam -genkey -name prime256v1 -noout -out server-ecdsa.key
# Examine the key
openssl ec -in server-ecdsa.key -text -noout
# Extract the public key
openssl ec -in server-ecdsa.key -pubout -out server-ecdsa.pub
Key Comparison
# Compare file sizes
ls -la server-rsa.key server-ecdsa.key
-rw------- 1 user user 1704 Feb 21 10:00 server-rsa.key # 2048-bit RSA
-rw------- 1 user user 227 Feb 21 10:00 server-ecdsa.key # P-256 ECDSA
The ECDSA key is much smaller but provides comparable security.
Securing Your Private Key
# Set strict permissions -- only the owner can read
chmod 600 server-rsa.key server-ecdsa.key
# Verify permissions
ls -la server-rsa.key server-ecdsa.key
Never store private keys in git repositories, shared directories, or anywhere publicly accessible. Never email a private key.
Think About It: What would happen if an attacker obtained your web server's private key? What could they do with it? How would you recover?
Creating a CSR (Certificate Signing Request)
The CSR contains your public key and the identity information (domain name, etc.) that you want the CA to certify.
Interactive CSR Creation
# Create a CSR interactively
openssl req -new -key server-rsa.key -out server.csr
You will be prompted for:
Country Name (2 letter code) [AU]: IN
State or Province Name [Some-State]: Karnataka
Locality Name []: Bangalore
Organization Name []: Example Corp
Organizational Unit Name []: Engineering
Common Name []: www.example.com
Email Address []: admin@example.com
Please enter the following 'extra' attributes:
A challenge password []: # Leave empty
An optional company name []: # Leave empty
Non-Interactive CSR Creation (Scriptable)
For automation, pass everything on the command line:
openssl req -new \
-key server-rsa.key \
-out server.csr \
-subj "/C=IN/ST=Karnataka/L=Bangalore/O=Example Corp/CN=www.example.com"
CSR with Subject Alternative Names (SAN)
Modern certificates require SANs. The Common Name (CN) alone is no longer sufficient -- browsers check SAN entries.
Create a configuration file first:
cat > san.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = IN
ST = Karnataka
L = Bangalore
O = Example Corp
CN = www.example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = www.example.com
DNS.2 = example.com
DNS.3 = api.example.com
EOF
# Generate CSR with SANs
openssl req -new -key server-rsa.key -out server-san.csr -config san.cnf
# Verify the CSR includes SANs
openssl req -in server-san.csr -text -noout | grep -A4 "Subject Alternative"
Examining a CSR
# View the full CSR details
openssl req -in server.csr -text -noout
Certificate Request:
Data:
Version: 1 (0x0)
Subject: C=IN, ST=Karnataka, L=Bangalore, O=Example Corp, CN=www.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
...
Signature Algorithm: sha256WithRSAEncryption
...
# Verify the CSR is valid (signature matches the key)
openssl req -in server.csr -verify -noout
verify OK
Self-Signed Certificates
Self-signed certificates are useful for development, testing, and internal services. They are NOT trusted by browsers (you will get a warning) because no CA vouched for them.
Quick Self-Signed Certificate
# Generate key and self-signed cert in one command
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout selfsigned.key \
-out selfsigned.crt \
-days 365 \
-subj "/C=IN/ST=Karnataka/O=Dev Team/CN=localhost"
Breaking down the flags:
-x509-- Output a certificate instead of a CSR-newkey rsa:2048-- Generate a new 2048-bit RSA key-nodes-- Do not encrypt the private key (no passphrase)-days 365-- Valid for one year-subj-- Non-interactive subject
Self-Signed with SANs
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout selfsigned.key \
-out selfsigned.crt \
-days 365 \
-subj "/C=IN/ST=Karnataka/O=Dev Team/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Using a Self-Signed Certificate from an Existing Key
# Create self-signed cert from an existing key and CSR
openssl x509 -req -in server.csr \
-signkey server-rsa.key \
-out server-selfsigned.crt \
-days 365
Examining Certificates
The openssl x509 command is your best friend for inspecting certificates.
View Full Certificate Details
openssl x509 -in selfsigned.crt -text -noout
This produces detailed output. The most important sections:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
4a:7b:c3:...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=IN, ST=Karnataka, O=Dev Team, CN=localhost
Validity
Not Before: Feb 21 10:00:00 2026 GMT
Not After : Feb 21 10:00:00 2027 GMT
Subject: C=IN, ST=Karnataka, O=Dev Team, CN=localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
...
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
Signature Algorithm: sha256WithRSAEncryption
...
Quick Extractions
# Just the subject
openssl x509 -in cert.pem -noout -subject
# Just the issuer
openssl x509 -in cert.pem -noout -issuer
# Just the validity dates
openssl x509 -in cert.pem -noout -dates
# Subject, issuer, and dates together
openssl x509 -in cert.pem -noout -subject -issuer -dates
# The fingerprint (SHA-256)
openssl x509 -in cert.pem -noout -fingerprint -sha256
# Serial number
openssl x509 -in cert.pem -noout -serial
# Check SANs
openssl x509 -in cert.pem -noout -ext subjectAltName
Check If a Certificate Matches a Key
This is a common troubleshooting step -- you have a .crt and a .key file but
are not sure they belong together.
# Compare the modulus hash of the certificate and key
openssl x509 -in server.crt -noout -modulus | openssl md5
openssl rsa -in server.key -noout -modulus | openssl md5
# If both MD5 hashes match, the cert and key go together
For ECDSA:
openssl x509 -in server.crt -noout -pubkey | openssl md5
openssl ec -in server.key -pubout 2>/dev/null | openssl md5
Checking Certificate Expiration
Expired certificates are the number one TLS-related outage. Check proactively.
# Check when a local certificate expires
openssl x509 -in /etc/ssl/certs/server.crt -noout -enddate
# Check when a remote server's certificate expires
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -enddate
# Check if a certificate will expire within the next 30 days
openssl x509 -in cert.pem -noout -checkend 2592000
# Exit code 0 = still valid; exit code 1 = will expire within 30 days
Hands-On: Certificate Expiration Monitoring Script
#!/bin/bash
# check_cert_expiry.sh -- Check certificate expiration for multiple domains
DOMAINS="example.com google.com github.com"
WARN_DAYS=30
WARN_SECONDS=$((WARN_DAYS * 86400))
for domain in $DOMAINS; do
expiry=$(echo | openssl s_client -connect "$domain:443" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$expiry" ]; then
echo "[ERROR] $domain - Could not retrieve certificate"
continue
fi
expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -lt 0 ]; then
echo "[EXPIRED] $domain - Expired $((-days_left)) days ago!"
elif [ "$days_left" -lt "$WARN_DAYS" ]; then
echo "[WARNING] $domain - Expires in $days_left days ($expiry)"
else
echo "[OK] $domain - Expires in $days_left days ($expiry)"
fi
done
chmod +x check_cert_expiry.sh
./check_cert_expiry.sh
[OK] example.com - Expires in 245 days (Oct 28 12:00:00 2026 GMT)
[OK] google.com - Expires in 67 days (Apr 29 08:30:00 2026 GMT)
[OK] github.com - Expires in 180 days (Aug 20 00:00:00 2026 GMT)
Verifying Certificate Chains
Verify a Certificate Against the System Trust Store
# Verify using the system's CA bundle
openssl verify cert.pem
If the issuing CA is in the system trust store, you will see:
cert.pem: OK
Verify with an Explicit CA Certificate
# Verify with a specific CA certificate
openssl verify -CAfile ca-cert.pem server-cert.pem
# Verify with both a root CA and an intermediate
openssl verify -CAfile root-ca.pem -untrusted intermediate.pem server-cert.pem
Testing TLS Connections with s_client
openssl s_client is one of the most useful debugging tools. It acts as a basic
TLS client.
# Basic TLS connection test
openssl s_client -connect example.com:443 -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Requested Signature Algorithms: ...
Peer certificate: CN=example.com
...
Verification: OK
Common s_client Options
# Show the full certificate chain
openssl s_client -connect example.com:443 -showcerts
# Test a specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Connect with SNI (Server Name Indication) -- important for shared hosting
openssl s_client -connect shared-host.com:443 -servername www.example.com
# Test with a specific CA bundle
openssl s_client -connect example.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt
# Send an HTTP request through the TLS connection
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" \
| openssl s_client -connect example.com:443 -quiet 2>/dev/null
# Check SMTP with STARTTLS
openssl s_client -connect smtp.gmail.com:587 -starttls smtp -brief
Think About It: Why is SNI important? What happens on a server hosting multiple HTTPS websites on a single IP address if the client does not send the SNI extension?
Creating a Mini CA (Certificate Authority)
This is one of the most instructive exercises in this entire book. You will create your own CA, sign a server certificate with it, and verify the chain.
Step 1: Create the Root CA
# Create a directory structure
mkdir -p ~/miniCA/{root,intermediate,server}
cd ~/miniCA
# Generate the root CA private key
openssl genrsa -aes256 -out root/root-ca.key 4096
# Enter a strong passphrase -- this is your root CA's crown jewel
# Create the root CA certificate (self-signed)
openssl req -x509 -new -nodes \
-key root/root-ca.key \
-sha256 -days 3650 \
-out root/root-ca.crt \
-subj "/C=IN/ST=Karnataka/O=MiniCA/CN=MiniCA Root CA"
# Verify the root CA certificate
openssl x509 -in root/root-ca.crt -text -noout | head -15
Notice that the Issuer and Subject are the same -- that is what makes it self-signed and a root certificate.
Step 2: Create an Intermediate CA
# Generate the intermediate CA key
openssl genrsa -aes256 -out intermediate/intermediate.key 4096
# Create a CSR for the intermediate CA
openssl req -new \
-key intermediate/intermediate.key \
-out intermediate/intermediate.csr \
-subj "/C=IN/ST=Karnataka/O=MiniCA/CN=MiniCA Intermediate CA"
Create a config file for intermediate CA extensions:
cat > intermediate/intermediate-ext.cnf << 'EOF'
[v3_intermediate_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF
# Sign the intermediate CSR with the root CA
openssl x509 -req \
-in intermediate/intermediate.csr \
-CA root/root-ca.crt \
-CAkey root/root-ca.key \
-CAcreateserial \
-out intermediate/intermediate.crt \
-days 1825 \
-sha256 \
-extfile intermediate/intermediate-ext.cnf \
-extensions v3_intermediate_ca
# Verify the intermediate cert was signed by the root
openssl verify -CAfile root/root-ca.crt intermediate/intermediate.crt
intermediate/intermediate.crt: OK
Step 3: Create and Sign a Server Certificate
# Generate the server key (no passphrase for server use)
openssl genrsa -out server/server.key 2048
# Create a CSR with SANs
cat > server/server-ext.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = IN
ST = Karnataka
O = Example Corp
CN = app.example.local
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = app.example.local
DNS.2 = *.example.local
IP.1 = 192.168.1.100
EOF
openssl req -new \
-key server/server.key \
-out server/server.csr \
-config server/server-ext.cnf
# Create a server cert extensions file
cat > server/server-sign-ext.cnf << 'EOF'
[server_cert]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.example.local,DNS:*.example.local,IP:192.168.1.100
EOF
# Sign the server CSR with the intermediate CA
openssl x509 -req \
-in server/server.csr \
-CA intermediate/intermediate.crt \
-CAkey intermediate/intermediate.key \
-CAcreateserial \
-out server/server.crt \
-days 365 \
-sha256 \
-extfile server/server-sign-ext.cnf \
-extensions server_cert
Step 4: Create the Certificate Chain and Verify
# Create the full chain file (intermediate + root)
cat intermediate/intermediate.crt root/root-ca.crt > server/chain.pem
# Create the full chain including the server cert
cat server/server.crt intermediate/intermediate.crt > server/fullchain.pem
# Verify the complete chain
openssl verify -CAfile root/root-ca.crt \
-untrusted intermediate/intermediate.crt \
server/server.crt
server/server.crt: OK
# See the chain in detail
echo "=== Server Cert ==="
openssl x509 -in server/server.crt -noout -subject -issuer
echo ""
echo "=== Intermediate Cert ==="
openssl x509 -in intermediate/intermediate.crt -noout -subject -issuer
echo ""
echo "=== Root CA Cert ==="
openssl x509 -in root/root-ca.crt -noout -subject -issuer
Expected output:
=== Server Cert ===
subject=C=IN, ST=Karnataka, O=Example Corp, CN=app.example.local
issuer=C=IN, ST=Karnataka, O=MiniCA, CN=MiniCA Intermediate CA
=== Intermediate Cert ===
subject=C=IN, ST=Karnataka, O=MiniCA, CN=MiniCA Intermediate CA
issuer=C=IN, ST=Karnataka, O=MiniCA, CN=MiniCA Root CA
=== Root CA Cert ===
subject=C=IN, ST=Karnataka, O=MiniCA, CN=MiniCA Root CA
issuer=C=IN, ST=Karnataka, O=MiniCA, CN=MiniCA Root CA
The chain is clear: Server --> Intermediate --> Root (self-signed).
Converting Between Formats
PEM to DER and Back
# PEM to DER
openssl x509 -in server/server.crt -outform DER -out server/server.der
# DER to PEM
openssl x509 -in server/server.der -inform DER -outform PEM -out server/server-back.pem
# Verify they are identical
diff <(openssl x509 -in server/server.crt -noout -modulus) \
<(openssl x509 -in server/server-back.pem -noout -modulus)
# No output means they match
Create a PKCS#12 Bundle
# Bundle cert + key + chain into a PKCS#12 file
openssl pkcs12 -export \
-out server/server.p12 \
-inkey server/server.key \
-in server/server.crt \
-certfile server/chain.pem \
-name "app.example.local"
# You will be prompted for an export password
# Extract everything from a PKCS#12
openssl pkcs12 -in server/server.p12 -out server/extracted.pem -nodes
# Enter the export password
PEM Key Format Conversion
# Convert a traditional RSA key to PKCS#8 format
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-in server/server.key -out server/server-pkcs8.key -nocrypt
# Convert PKCS#8 back to traditional format
openssl rsa -in server/server-pkcs8.key -out server/server-traditional.key
Debug This
You configure Nginx with a new certificate, but it refuses to start:
nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/ssl/private/server.key") failed
nginx: [emerg] cannot load certificate key "/etc/ssl/private/server.key":
error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch
How do you diagnose and fix this?
Answer: The certificate and private key do not match. They were probably generated separately or mixed up during a renewal. Check the modulus of both:
# Check the certificate's public key
openssl x509 -in /etc/ssl/certs/server.crt -noout -modulus | openssl md5
# Check the private key's corresponding public key
openssl rsa -in /etc/ssl/private/server.key -noout -modulus | openssl md5
If the MD5 hashes are different, the files do not go together. You need to either find the correct matching key, or generate a new key and CSR and get a new certificate.
What Just Happened?
+------------------------------------------------------------------+
| OPENSSL HANDS-ON |
+------------------------------------------------------------------+
| |
| KEY GENERATION: |
| RSA: openssl genrsa -out key.pem 2048 |
| ECDSA: openssl ecparam -genkey -name prime256v1 -noout -out |
| |
| CSR CREATION: |
| openssl req -new -key key.pem -out request.csr |
| Use -config with SAN for modern certificates |
| |
| SELF-SIGNED: |
| openssl req -x509 -newkey rsa:2048 -nodes ... |
| |
| EXAMINING CERTS: |
| openssl x509 -in cert.pem -text -noout |
| -subject, -issuer, -dates, -fingerprint |
| |
| CERT MATCHING: |
| Compare modulus MD5: cert vs key must match |
| |
| CHAIN VERIFICATION: |
| openssl verify -CAfile root.pem [-untrusted inter.pem] cert |
| |
| TLS TESTING: |
| openssl s_client -connect host:443 -brief |
| |
| MINI CA: |
| Root CA --> signs Intermediate --> signs Server cert |
| fullchain.pem = server cert + intermediate(s) |
| |
| FORMAT CONVERSION: |
| PEM <-> DER: openssl x509 -outform DER/PEM |
| PKCS#12: openssl pkcs12 -export / -in |
| |
+------------------------------------------------------------------+
Try This
Exercise 1: Full Certificate Lifecycle
Perform the complete lifecycle without looking at the chapter:
- Generate an ECDSA private key
- Create a CSR with SANs for
myapp.localand*.myapp.local - Self-sign it for 90 days
- Examine the certificate and verify the SANs are present
- Check the expiration date
Exercise 2: Mini CA Expansion
Using the mini CA you created in this chapter, sign certificates for three different services: a web server, a database, and an API gateway. Give each one different SANs. Verify all three against the chain.
Exercise 3: Certificate Detective
Pick any website and extract as much information as you can using only
openssl s_client and openssl x509:
- TLS version and cipher suite
- Full certificate chain with subjects and issuers
- Key type and size
- SANs
- Exact expiration timestamp
- Certificate fingerprint
Bonus Challenge
Write a script that takes a domain name as an argument and produces a formatted report of all the TLS and certificate information for that domain. Include the certificate chain, key details, expiration, and whether the certificate will expire within the next 30 days.