OpenSSL is the industry-standard toolkit for TLS/SSL certificate management. Whether you're generating self-signed certificates for development, creating Certificate Signing Requests for production, or debugging certificate issues, OpenSSL is essential. This guide covers practical SSL/TLS certificate management from a senior developer's perspective.
Why Understand OpenSSL
Every developer should know OpenSSL because:
- Local Development: Self-signed certs for HTTPS testing
- Production Deployments: CSR generation for CA certificates
- Debugging: Verify certificates and connections
- Security Audits: Inspect certificate chains and expiration
- Automation: Script certificate management
Certificate Basics
Key Concepts
- Private Key: Secret key; never share (
.key) - Public Key: Derived from private key; safe to share
- CSR: Certificate Signing Request, sent to CA (
.csr) - Certificate: Signed public key with metadata (
.crt, .pem) - CA: Certificate Authority, trusted signer
- Chain: CA certificates linking to root CA
File Formats
| Extension | Format | Contains |
|---|
.pem | Base64-encoded | Keys, certs, or both |
.crt | Usually PEM | Certificate only |
.key | Usually PEM | Private key only |
.der | Binary | Certificate |
.p12/.pfx | Binary | Key + cert bundle |
Generate Self-Signed Certificates
Quick Self-Signed Certificate
# Generate the key and certificate in one command
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout server.key \
-out server.crt \
-days 365 \
-subj "/CN=localhost/O=Development/C=US"
With Subject Alternative Names (SAN)
Modern browsers require SAN for localhost:
# Create a config file
cat > san.cnf << 'EOF'
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = localhost
O = Development
C = US
[v3_req]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
DNS.3 = myapp.local
IP.1 = 127.0.0.1
IP.2 = ::1
EOF
# Generate a certificate
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout server.key \
-out server.crt \
-days 365 \
-config san.cnf \
-extensions v3_req
Create a Local Certificate Authority
For development environments needing multiple certificates:
Step 1: Create CA
##!/bin/bash
OUTPUT_FOLDER=./certs
CA_DOMAIN="ca.local"
COMPANY="MyCompany"
COUNTRY="US"
mkdir -p $OUTPUT_FOLDER
# CA configuration
cat > $OUTPUT_FOLDER/ca.cnf << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
x509_extensions = v3_req
prompt = no
encrypt_key = no
[req_distinguished_name]
CN = $CA_DOMAIN
O = $COMPANY
C = $COUNTRY
[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = critical, CA:TRUE, pathlen:3
keyUsage = critical, cRLSign, keyCertSign
nsCertType = sslCA, emailCA
EOF
# Generate a CA certificate
openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -nodes \
-config $OUTPUT_FOLDER/ca.cnf \
-keyout $OUTPUT_FOLDER/ca.key \
-out $OUTPUT_FOLDER/ca.crt
echo "CA certificate created: $OUTPUT_FOLDER/ca.crt"
Step 2: Sign Certificates with CA
##!/bin/bash
DOMAIN=$1 # e.g., myapp.local
OUTPUT_FOLDER=./certs
# Server certificate configuration
cat > $OUTPUT_FOLDER/$DOMAIN.cnf << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
x509_extensions = v3_ca
prompt = no
encrypt_key = no
default_bits = 4096
[req_distinguished_name]
CN = $DOMAIN
O = MyCompany
C = US
[v3_req]
basicConstraints = critical, CA:FALSE
[v3_ca]
basicConstraints = critical, CA:FALSE
authorityKeyIdentifier = keyid:always, issuer:always
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:$DOMAIN, DNS:*.$DOMAIN
EOF
# Generate a CSR
openssl req -new -sha256 \
-keyout $OUTPUT_FOLDER/$DOMAIN.key \
-out $OUTPUT_FOLDER/$DOMAIN.csr \
-config $OUTPUT_FOLDER/$DOMAIN.cnf
# Sign with CA
openssl x509 -req -sha256 -days 365 \
-in $OUTPUT_FOLDER/$DOMAIN.csr \
-CA $OUTPUT_FOLDER/ca.crt \
-CAkey $OUTPUT_FOLDER/ca.key \
-CAcreateserial \
-out $OUTPUT_FOLDER/$DOMAIN.crt \
-extensions v3_ca \
-extfile $OUTPUT_FOLDER/$DOMAIN.cnf
# Create the full chain
cat $OUTPUT_FOLDER/$DOMAIN.crt $OUTPUT_FOLDER/ca.crt > $OUTPUT_FOLDER/$DOMAIN.fullchain.crt
# Clean up
rm $OUTPUT_FOLDER/$DOMAIN.csr $OUTPUT_FOLDER/$DOMAIN.cnf
echo "Certificate created: $OUTPUT_FOLDER/$DOMAIN.crt"
Step 3: Trust CA Certificate
Linux (CentOS/RHEL):
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/myca.crt
sudo update-ca-trust
Linux (Ubuntu/Debian):
sudo cp ca.crt /usr/local/share/ca-certificates/myca.crt
sudo update-ca-certificates
Linux (openSUSE):
sudo cp ca.crt /usr/share/pki/trust/anchors/myca.crt
sudo update-ca-certificates --force
macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt
Firefox: Navigate to about:preferences → Privacy & Security → View Certificates → Authorities → Import
Chrome: Navigate to chrome://settings/certificates → Authorities → Import
Generate CSR for Production
Create CSR for CA Signing
# Generate a private key
openssl genrsa -out example.com.key 4096
# Generate a CSR
openssl req -new -key example.com.key -out example.com.csr \
-subj "/CN=example.com/O=My Company/L=New York/ST=NY/C=US"
# Verify the CSR
openssl req -text -noout -verify -in example.com.csr
CSR with SAN
# Create a config
cat > csr.cnf << 'EOF'
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = example.com
O = My Company
L = New York
ST = NY
C = US
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
EOF
# Generate a CSR with SAN
openssl req -new -key example.com.key -out example.com.csr -config csr.cnf
Inspect Certificates
View Certificate Details
# View the local certificate
openssl x509 -text -noout -in certificate.crt
# View the remote certificate
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -text -noout
# Check the expiration date
openssl x509 -enddate -noout -in certificate.crt
# View the certificate chain
openssl s_client -connect example.com:443 -showcerts
Verify Certificate and Key Match
# Compare modulus
openssl x509 -modulus -noout -in certificate.crt | md5sum
openssl rsa -modulus -noout -in private.key | md5sum
# Both should match
Check Certificate Chain
# Verify against CA
openssl verify -CAfile ca.crt server.crt
# Verify the full chain
openssl verify -CAfile ca-bundle.crt -untrusted intermediate.crt server.crt
Convert Between Formats
PEM to DER
openssl x509 -outform der -in certificate.pem -out certificate.der
DER to PEM
openssl x509 -inform der -in certificate.der -out certificate.pem
Create PKCS12 Bundle
# Combine the key and cert into .p12
openssl pkcs12 -export \
-out certificate.p12 \
-inkey private.key \
-in certificate.crt \
-certfile ca.crt
# Extract from .p12
openssl pkcs12 -in certificate.p12 -out extracted.pem -nodes
Test SSL Connections
Test HTTPS Server
# Basic connection test
openssl s_client -connect example.com:443
# With SNI (required for most servers)
openssl s_client -connect example.com:443 -servername example.com
# Test a specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Check supported ciphers
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
Test SMTP with STARTTLS
openssl s_client -connect mail.example.com:587 -starttls smtp
Using Certificates
Nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
}
Node.js
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
ca: fs.readFileSync('ca.crt') // Optional: for client cert verification
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Secure!');
}).listen(443);
Docker Compose
services:
nginx:
image: nginx:alpine
volumes:
- ./certs/server.crt:/etc/nginx/ssl/server.crt:ro
- ./certs/server.key:/etc/nginx/ssl/server.key:ro
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0 # For development only!
Key Takeaways
- SAN is required: Modern browsers need Subject Alternative Names
- Local CA for teams: Share one CA, generate multiple certs
- Never commit keys: Add
*.key to .gitignore - Test connections: Use
openssl s_client for debugging - Match key and cert: Verify modulus before deploying
- Automate renewal: Track expiration, script regeneration
OpenSSL is the foundation of TLS/SSL management—mastering these commands saves hours of debugging and enables secure development workflows.